Jon
Jon

Reputation: 95

Query IEnumerable as IEnumerable<Type>

I have a problem I need to solve efficiently.

I require the index of an element in an IEnumerable source, one way I could do this is with the following

    var items = source.Cast<ObjectType>().Where(obj => obj.Start == forDate);

This would give me an IEnumerable of all the items that match the predicate.

   if(items != null && items.Any()){
       // I now need the ordinal from the original list
       return source.IndexOf(items[0]);
   }

However, the list could be vast and the operation will be carried out many times. I believe this is inefficient and there must be a better way to do this.

I would be grateful if anyone can point me in the correct direction.

Upvotes: 1

Views: 165

Answers (4)

Enigmativity
Enigmativity

Reputation: 117084

If you need to do this multiple times I would create a lookup for the indices.

ILookup<DateTime, int> lookup =
    source
        .Cast<ObjectType>()
        .Select((e, i) => new { e, i })
        .ToLookup(x => x.e.Start, x => x.i);

Now given a forDate you can do this:

IEnumerable<int> indices = lookup[forDate];

Since the lookup is basically like a dictionary that returns multiple values you get the results instantly. So repeating this for multiple values is super fast.

And since this returns IEnumerable<int> you know when there are duplicate values within the source list. If you only need the first one then just do a .First().

Upvotes: 1

Dennis_E
Dennis_E

Reputation: 8894

Your second code sample was invalid: since items is an IEnumerable, you cannot call items[0]. You can use First(). Anyway:

var items = source.Cast<ObjectType>()
.Select((item, index) => new KeyValuePair<int, ObjectType>(index, item))
.Where(obj => obj.Value.Start == forDate);

and then:

if (items != null && items.Any()) {
    return items.First().Key;
}

Upvotes: 1

acelent
acelent

Reputation: 8135

Using Linq, you can take the index of each object before filtering them:

source
    .Cast<ObjectType>()
    .Select((obj, i) => new { Obj = obj, I = i })
    .Where(x => x.Obj.Start == forDate)
    .Select(x => x.I)
    .FirstOrDefault();

However, this is not really efficient, the following will do the same without allocations:

int i = 0;
foreach (ObjectType obj in source)
{
    if (obj.Start == forDate)
    {
        return i;
    }
    i++;
}

Upvotes: 2

Kevin Gosse
Kevin Gosse

Reputation: 39007

Sometimes, it's good to forget about Linq and go back to basics:

int index = 0;

foeach (ObjectType element in source)
{
    if (element.Start == forDate)
    {
        return index;
    }

    index++;
}

// No element found

Upvotes: 2

Related Questions