Jader Dias
Jader Dias

Reputation: 90475

How to get the index of an element in an IEnumerable?

I wrote this:

public static class EnumerableExtensions
{
    public static int IndexOf<T>(this IEnumerable<T> obj, T value)
    {
        return obj
            .Select((a, i) => (a.Equals(value)) ? i : -1)
            .Max();
    }

    public static int IndexOf<T>(this IEnumerable<T> obj, T value
           , IEqualityComparer<T> comparer)
    {
        return obj
            .Select((a, i) => (comparer.Equals(a, value)) ? i : -1)
            .Max();
    }
}

But I don't know if it already exists, does it?

Upvotes: 182

Views: 307425

Answers (14)

AndyG
AndyG

Reputation: 41090

Thankfully the language has added support for IEnumerable.Index() as of .NET 9 that returns an enumerable of ValueTuple like so: IEnumerable<(int Index, TSource Item)>

Example usage:

public class Program
{
    public static void Main()
    {
        PrintIndexes([1, 2, 3, 4]);
    }
    
    public static void PrintIndexes(IEnumerable<int> collection)
    {
        foreach(var (index, item) in collection.Index())
        {
            System.Console.WriteLine($"{index}: {item}");
        }
    }
}

Output:

0: 1
1: 2
2: 3
3: 4

You'd use .Index() to simplify the answer from Marc Gravell like this:

public static int IndexOf<T>(this IEnumerable<T> source, T value)
{
    var comparer = EqualityComparer<T>.Default; // or pass in as a parameter
    foreach (var (index, item) in source.Index())
    {
        if (comparer.Equals(item, value))
          return index;
    }
    return -1;
})

Upvotes: -1

NotEnoughData
NotEnoughData

Reputation: 318

You can add a small extension method:

public static IEnumerable<(T value, int index)> Index<T>(this IEnumerable<T> source)
    => source.Select((x, i) => (value: x, index: i));

And then you can use it like:

// get the index of a particular element
myCollection.Index().First(x => x.value == myElement).index;

// loop through elements with indices
foreach (var (element, i) in myCollection.Index()) {
    // do whatever
}

// get the original index of the 3rd item that matches some criteria
myCollection.Index().Where(/* some condition */).ElementAt(2).index

Upvotes: 1

AFatNiBBa
AFatNiBBa

Reputation: 79

Try this:

static int FindIndex<T>(this IEnumerable<T> a, Predicate<T> f) =>
    a.TakeWhile(x => !f(x)).Count();

static int IndexOf<T>(this IEnumerable<T> a, T value) =>
    a.FindIndex(x => EqualityComparer<T>.Default.Equals(x, value));

var i = new[] { 1, 2, 3 }.IndexOf(2); // 1

Upvotes: -2

Dave
Dave

Reputation: 3017

Stumbled across this today in a search for answers and I thought I'd add my version to the list (No pun intended). It utlises the null conditional operator of c#6.0

IEnumerable<Item> collection = GetTheCollection();

var index = collection
.Select((item,idx) => new { Item = item, Index = idx })
//or .FirstOrDefault(_ =>  _.Item.Prop == something)
.FirstOrDefault(_ => _.Item == itemToFind)?.Index ?? -1;

I've done some 'racing of the old horses' (testing) and for large collections (~100,000), worst case scenario that item you want is at the end, this is 2x faster than doing ToList().FindIndex(). If the Item you want is in the middle its ~4x faster.

For smaller collections (~10,000) it seems to be only marginally faster

Heres how I tested it https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e

Upvotes: 4

Yom T.
Yom T.

Reputation: 9180

This can get really cool with an extension (functioning as a proxy), for example:

collection.SelectWithIndex(); 
// vs. 
collection.Select((item, index) => item);

Which will automagically assign indexes to the collection accessible via this Index property.

Interface:

public interface IIndexable
{
    int Index { get; set; }
}

Custom extension (probably most useful for working with EF and DbContext):

public static class EnumerableXtensions
{
    public static IEnumerable<TModel> SelectWithIndex<TModel>(
        this IEnumerable<TModel> collection) where TModel : class, IIndexable
    {
        return collection.Select((item, index) =>
        {
            item.Index = index;
            return item;
        });
    }
}

public class SomeModelDTO : IIndexable
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public int Index { get; set; }
}

// In a method
var items = from a in db.SomeTable
            where a.Id == someValue
            select new SomeModelDTO
            {
                Id = a.Id,
                Name = a.Name,
                Price = a.Price
            };

return items.SelectWithIndex()
            .OrderBy(m => m.Name)
            .Skip(pageStart)
            .Take(pageSize)
            .ToList();

Upvotes: 0

daniele3004
daniele3004

Reputation: 13910

The best way to catch the position is by FindIndex This function is available only for List<>

Example

int id = listMyObject.FindIndex(x => x.Id == 15); 

If you have enumerator or array use this way

int id = myEnumerator.ToList().FindIndex(x => x.Id == 15); 

or

 int id = myArray.ToList().FindIndex(x => x.Id == 15); 

Upvotes: 8

Greg
Greg

Reputation: 121

A few years later, but this uses Linq, returns -1 if not found, doesn't create extra objects, and should short-circuit when found [as opposed to iterating over the entire IEnumerable]:

public static int IndexOf<T>(this IEnumerable<T> list, T item)
{
    return list.Select((x, index) => EqualityComparer<T>.Default.Equals(item, x)
                                     ? index
                                     : -1)
               .FirstOr(x => x != -1, -1);
}

Where 'FirstOr' is:

public static T FirstOr<T>(this IEnumerable<T> source, T alternate)
{
    return source.DefaultIfEmpty(alternate)
                 .First();
}

public static T FirstOr<T>(this IEnumerable<T> source, Func<T, bool> predicate, T alternate)
{
    return source.Where(predicate)
                 .FirstOr(alternate);
}

Upvotes: 5

MaxOvrdrv
MaxOvrdrv

Reputation: 1916

A bit late in the game, i know... but this is what i recently did. It is slightly different than yours, but allows the programmer to dictate what the equality operation needs to be (predicate). Which i find very useful when dealing with different types, since i then have a generic way of doing it regardless of object type and <T> built in equality operator.

It also has a very very small memory footprint, and is very, very fast/efficient... if you care about that.

At worse, you'll just add this to your list of extensions.

Anyway... here it is.

 public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
 {
     int retval = -1;
     var enumerator = source.GetEnumerator();

     while (enumerator.MoveNext())
     {
         retval += 1;
         if (predicate(enumerator.Current))
         {
             IDisposable disposable = enumerator as System.IDisposable;
             if (disposable != null) disposable.Dispose();
             return retval;
         }
     }
     IDisposable disposable = enumerator as System.IDisposable;
     if (disposable != null) disposable.Dispose();
     return -1;
 }

Hopefully this helps someone.

Upvotes: 5

Mark Watts
Mark Watts

Reputation: 764

The way I'm currently doing this is a bit shorter than those already suggested and as far as I can tell gives the desired result:

 var index = haystack.ToList().IndexOf(needle);

It's a bit clunky, but it does the job and is fairly concise.

Upvotes: 27

Joshka
Joshka

Reputation: 886

An alternative to finding the index after the fact is to wrap the Enumerable, somewhat similar to using the Linq GroupBy() method.

public static class IndexedEnumerable
{
    public static IndexedEnumerable<T> ToIndexed<T>(this IEnumerable<T> items)
    {
        return IndexedEnumerable<T>.Create(items);
    }
}

public class IndexedEnumerable<T> : IEnumerable<IndexedEnumerable<T>.IndexedItem>
{
    private readonly IEnumerable<IndexedItem> _items;

    public IndexedEnumerable(IEnumerable<IndexedItem> items)
    {
        _items = items;
    }

    public class IndexedItem
    {
        public IndexedItem(int index, T value)
        {
            Index = index;
            Value = value;
        }

        public T Value { get; private set; }
        public int Index { get; private set; }
    }

    public static IndexedEnumerable<T> Create(IEnumerable<T> items)
    {
        return new IndexedEnumerable<T>(items.Select((item, index) => new IndexedItem(index, item)));
    }

    public IEnumerator<IndexedItem> GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Which gives a use case of:

var items = new[] {1, 2, 3};
var indexedItems = items.ToIndexed();
foreach (var item in indexedItems)
{
    Console.WriteLine("items[{0}] = {1}", item.Index, item.Value);
}

Upvotes: 1

Axente Adrian
Axente Adrian

Reputation: 139

I think the best option is to implement like this:

public static int IndexOf<T>(this IEnumerable<T> enumerable, T element, IEqualityComparer<T> comparer = null)
{
    int i = 0;
    comparer = comparer ?? EqualityComparer<T>.Default;
    foreach (var currentElement in enumerable)
    {
        if (comparer.Equals(currentElement, element))
        {
            return i;
        }

        i++;
    }

    return -1;
}

It will also not create the anonymous object

Upvotes: 13

dahlbyk
dahlbyk

Reputation: 77520

I would implement it like this:

public static class EnumerableExtensions
{
    public static int IndexOf<T>(this IEnumerable<T> obj, T value)
    {
        return obj.IndexOf(value, null);
    }

    public static int IndexOf<T>(this IEnumerable<T> obj, T value, IEqualityComparer<T> comparer)
    {
        comparer = comparer ?? EqualityComparer<T>.Default;
        var found = obj
            .Select((a, i) => new { a, i })
            .FirstOrDefault(x => comparer.Equals(x.a, value));
        return found == null ? -1 : found.i;
    }
}

Upvotes: 33

Scott Dorman
Scott Dorman

Reputation: 42516

The whole point of getting things out as IEnumerable is so you can lazily iterate over the contents. As such, there isn't really a concept of an index. What you are doing really doesn't make a lot of sense for an IEnumerable. If you need something that supports access by index, put it in an actual list or collection.

Upvotes: 59

Marc Gravell
Marc Gravell

Reputation: 1062745

I'd question the wisdom, but perhaps:

source.TakeWhile(x => x != value).Count();

(using EqualityComparer<T>.Default to emulate != if needed) - but you need to watch to return -1 if not found... so perhaps just do it the long way

public static int IndexOf<T>(this IEnumerable<T> source, T value)
{
    int index = 0;
    var comparer = EqualityComparer<T>.Default; // or pass in as a parameter
    foreach (T item in source)
    {
        if (comparer.Equals(item, value)) return index;
        index++;
    }
    return -1;
}

Upvotes: 152

Related Questions