Ervis Trupja
Ervis Trupja

Reputation: 2800

Select given ranges of indexes from source array

I want to create an array with the taken indexes of an array. Let's suppose I have an array like this:

| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

And the result I get from my query looks like:

enter image description here

Which means that places 1, 3, 4 (LengthUnit = 2)and 7 are busy (already taken). So, the array now would look like:

| T | 2 | T | T | 5 | 6 | T | 8 | 9 | 10 |

Where, T stands for taken.

How can I create two arrays of integers using the result from query which would look like:

int[] taken = { 1, 3, 4, 7 };
int[] notTaken = { 2, 5, 6, 8, 9, 10 };

Upvotes: 1

Views: 110

Answers (3)

Gilad Green
Gilad Green

Reputation: 37299

Use Enumerable.Range to create the collection of items you want. Then use Except to get the others.

List<int> values = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

List<Tuple<int, int>> ranges = new List<Tuple<int, int>>
{
    new Tuple<int, int>(1,1),
    new Tuple<int, int>(3,2),
    new Tuple<int, int>(7,1),
};

var t = ranges.SelectMany(range => Enumerable.Range(range.Item1, range.Item2)).ToList();

// Here I decide to use Intersect instead of just having t for the case 
// where you requested a range that doesn't exist
var taken = values.Intersect(t).ToArray();
var notTaken = values.Except(taken).ToList();

In the case that you want the values and that they aren't sequential like in the example then instead: create a collection of all desired indexes and then get all items of those indexes:

var indexes = ranges.SelectMany(range => Enumerable.Range(range.Item1, range.Item2)).Select(item => item - 1).ToList();

var taken = values.Where((item, index) => indexes.Contains(index)).ToList();
var notTaken = values.Where((item, index) => !indexes.Contains(index)).ToList();

Upvotes: 2

CodeCaster
CodeCaster

Reputation: 151586

A little Linq will get you a long way:

public class QueryResult
{
    public int StartUnit { get; set; }
    public int LengthUnit { get; set; }
}

var input = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var queryResult = new QueryResult[]
{
    new QueryResult { StartUnit = 1, LengthUnit = 1 },
    new QueryResult { StartUnit = 3, LengthUnit = 2 },
    new QueryResult { StartUnit = 7, LengthUnit = 1 },
};

var taken = new List<int>();

taken.AddRange(queryResult.SelectMany(q => (input.Skip(q.StartUnit - 1).Take(q.LengthUnit))));

Console.WriteLine("Taken: {0}", string.Join(",", taken));

var notTaken = input.Except(taken);

Console.WriteLine("Not taken: {0}", string.Join(",", notTaken));

Using SelectMany(), Skip() and Take(), you can select the ranges that you wish to include. Using Except() you can then get the items not taken.

Note that this will perform horribly, as it iterates the collections way too many times. It also assumes that the StartUnit actually denotes an (index + 1) in the input collection, not a value.

If you don't actually want to look at the input array, and the values are always contiguous (i.e. no holes in the input), you can use Enumerable.Range() to generate the requested ranges:

taken.AddRange(queryResult.SelectMany(q => Enumerable.Range(q.StartUnit, q.LengthUnit)));

And generate the full range for the Except() to exclude from:

var notTaken = Enumerable.Range(1, 10).Except(taken);

And of course if you want the output to actually be arrays, do a call to ToArray() here and there.

Upvotes: 1

Patrick Hofman
Patrick Hofman

Reputation: 156958

Enumerable.Range proofs to be useful in this case:

Dictionary<int, int> startAndLength = new Dictionary<int, int>()
                                      { { 1, 1 }, { 3, 2 }, { 7, 1 } };

int[] taken = startAndLength
              .SelectMany(kvp => Enumerable.Range(kvp.Key, kvp.Value)).ToArray();

int[] notTaken = Enumerable.Range(0, 10).Except(taken).ToArray();

Start creating a set of starts and lengths, then determine the taken items using Enumerable.Range. Then use Enumerable.Range again to determine the items not taken.

Upvotes: 5

Related Questions