How to get index using LINQ?

C#.NetLinqC# 3.0

C# Problem Overview


Given a datasource like that:

var c = new Car[]
{
  new Car{ Color="Blue", Price=28000},
  new Car{ Color="Red", Price=54000},
  new Car{ Color="Pink", Price=9999},
  // ..
};

How can I find the index of the first car satisfying a certain condition with LINQ?

EDIT:

I could think of something like this but it looks horrible:

int firstItem = someItems.Select((item, index) => new    
{    
    ItemName = item.Color,    
    Position = index    
}).Where(i => i.ItemName == "purple")    
  .First()    
  .Position;

Will it be the best to solve this with a plain old loop?

C# Solutions


Solution 1 - C#

myCars.Select((v, i) => new {car = v, index = i}).First(myCondition).index;

or the slightly shorter

myCars.Select((car, index) => new {car, index}).First(myCondition).index;

or the slightly shorter shorter

myCars.Select((car, index) => (car, index)).First(myCondition).index;

Solution 2 - C#

Simply do :

int index = List.FindIndex(your condition);

E.g.

int index = cars.FindIndex(c => c.ID == 150);

Solution 3 - C#

An IEnumerable is not an ordered set.
Although most IEnumerables are ordered, some (such as Dictionary or HashSet) are not.

Therefore, LINQ does not have an IndexOf method.

However, you can write one yourself:

///<summary>Finds the index of the first item matching an expression in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="predicate">The expression to test the items against.</param>
///<returns>The index of the first matching item, or -1 if no items match.</returns>
public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> predicate) {
	if (items == null) throw new ArgumentNullException("items");
	if (predicate == null) throw new ArgumentNullException("predicate");

	int retVal = 0;
	foreach (var item in items) {
		if (predicate(item)) return retVal;
		retVal++;
	}
	return -1;
}
///<summary>Finds the index of the first occurrence of an item in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="item">The item to find.</param>
///<returns>The index of the first matching item, or -1 if the item was not found.</returns>
public static int IndexOf<T>(this IEnumerable<T> items, T item) { return items.FindIndex(i => EqualityComparer<T>.Default.Equals(item, i)); }

Solution 4 - C#

myCars.TakeWhile(car => !myCondition(car)).Count();

It works! Think about it. The index of the first matching item equals the number of (not matching) item before it.

Story time

I too dislike the horrible standard solution you already suggested in your question. Like the accepted answer I went for a plain old loop although with a slight modification:

public static int FindIndex<T>(this IEnumerable<T> items, Predicate<T> predicate) {
    int index = 0;
    foreach (var item in items) {
        if (predicate(item)) break;
        index++;
    }
    return index;
}

Note that it will return the number of items instead of -1 when there is no match. But let's ignore this minor annoyance for now. In fact the horrible standard solution crashes in that case and I consider returning an index that is out-of-bounds superior.

What happens now is ReSharper telling me Loop can be converted into LINQ-expression. While most of the time the feature worsens readability, this time the result was awe-inspiring. So Kudos to the JetBrains.

Analysis

Pros
  • Concise
  • Combinable with other LINQ
  • Avoids newing anonymous objects
  • Only evaluates the enumerable until the predicate matches for the first time

Therefore I consider it optimal in time and space while remaining readable.

Cons
  • Not quite obvious at first
  • Does not return -1 when there is no match

Of course you can always hide it behind an extension method. And what to do best when there is no match heavily depends on the context.

Solution 5 - C#

I will make my contribution here... why? just because :p Its a different implementation, based on the Any LINQ extension, and a delegate. Here it is:

public static class Extensions
{
   	public static int IndexOf<T>(
            this IEnumerable<T> list, 
            Predicate<T> condition) {        		
        int i = -1;
        return list.Any(x => { i++; return condition(x); }) ? i : -1;
   	}
}

void Main()
{
	TestGetsFirstItem();
	TestGetsLastItem();
	TestGetsMinusOneOnNotFound();
	TestGetsMiddleItem();	
	TestGetsMinusOneOnEmptyList();
}

void TestGetsFirstItem()
{
	// Arrange
	var list = new string[] { "a", "b", "c", "d" };
	
	// Act
	int index = list.IndexOf(item => item.Equals("a"));
	
	// Assert
	if(index != 0)
	{
		throw new Exception("Index should be 0 but is: " + index);
	}
	
	"Test Successful".Dump();
}

void TestGetsLastItem()
{
	// Arrange
	var list = new string[] { "a", "b", "c", "d" };
	
	// Act
	int index = list.IndexOf(item => item.Equals("d"));
	
	// Assert
	if(index != 3)
	{
		throw new Exception("Index should be 3 but is: " + index);
	}
	
	"Test Successful".Dump();
}

void TestGetsMinusOneOnNotFound()
{
	// Arrange
	var list = new string[] { "a", "b", "c", "d" };
	
	// Act
	int index = list.IndexOf(item => item.Equals("e"));
	
	// Assert
	if(index != -1)
	{
		throw new Exception("Index should be -1 but is: " + index);
	}
	
	"Test Successful".Dump();
}

void TestGetsMinusOneOnEmptyList()
{
	// Arrange
	var list = new string[] {  };
	
	// Act
	int index = list.IndexOf(item => item.Equals("e"));
	
	// Assert
	if(index != -1)
	{
		throw new Exception("Index should be -1 but is: " + index);
	}
	
	"Test Successful".Dump();
}

void TestGetsMiddleItem()
{
	// Arrange
	var list = new string[] { "a", "b", "c", "d", "e" };
	
	// Act
	int index = list.IndexOf(item => item.Equals("c"));
	
	// Assert
	if(index != 2)
	{
		throw new Exception("Index should be 2 but is: " + index);
	}
	
	"Test Successful".Dump();
}        

Solution 6 - C#

Here is a little extension I just put together.

public static class PositionsExtension
{
    public static Int32 Position<TSource>(this IEnumerable<TSource> source,
                                          Func<TSource, bool> predicate)
    {
        return Positions<TSource>(source, predicate).FirstOrDefault();
    }
    public static IEnumerable<Int32> Positions<TSource>(this IEnumerable<TSource> source, 
                                                        Func<TSource, bool> predicate)
    {
        if (typeof(TSource) is IDictionary)
        {
            throw new Exception("Dictionaries aren't supported");
        }

        if (source == null)
        {
            throw new ArgumentOutOfRangeException("source is null");
        }
        if (predicate == null)
        {
            throw new ArgumentOutOfRangeException("predicate is null");
        }
        var found = source.Where(predicate).First();
        var query = source.Select((item, index) => new
            {
                Found = ReferenceEquals(item, found),
                Index = index

            }).Where( it => it.Found).Select( it => it.Index);
        return query;
    }
}

Then you can call it like this.

IEnumerable<Int32> indicesWhereConditionIsMet = 
      ListItems.Positions(item => item == this);

Int32 firstWelcomeMessage ListItems.Position(msg =>               
      msg.WelcomeMessage.Contains("Hello"));

Solution 7 - C#

Here's an implementation of the highest-voted answer that returns -1 when the item is not found:

public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> predicate)
{
	var itemsWithIndices = items.Select((item, index) => new { Item = item, Index = index });
	var matchingIndices =
		from itemWithIndex in itemsWithIndices
		where predicate(itemWithIndex.Item)
		select (int?)itemWithIndex.Index;

	return matchingIndices.FirstOrDefault() ?? -1;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncodymanixView Question on Stackoverflow
Solution 1 - C#Yuriy FaktorovichView Answer on Stackoverflow
Solution 2 - C#Red SwanView Answer on Stackoverflow
Solution 3 - C#SLaksView Answer on Stackoverflow
Solution 4 - C#Jonas BötelView Answer on Stackoverflow
Solution 5 - C#Marcel Valdez OrozcoView Answer on Stackoverflow
Solution 6 - C#jwizeView Answer on Stackoverflow
Solution 7 - C#SamView Answer on Stackoverflow