¿Cómo obtener un índice usando LINQ?

Dado un origen de datos como ese:

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

¿Cómo puedo encontrar el índice del primer automóvil que cumple una determinada condición con LINQ?

EDITAR:

Podría pensar en algo como esto, pero se ve horrible:

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

¿Será lo mejor para resolver esto con un simple bucle viejo?

Un IEnumerable no es un conjunto ordenado.
Aunque la mayoría de los IEnumerables están ordenados, algunos (como Dictionary o HashSet ) no lo son.

Por lo tanto, LINQ no tiene un método IndexOf .

Sin embargo, puedes escribir uno tú mismo:

 ///Finds the index of the first item matching an expression in an enumerable. ///The enumerable to search. ///The expression to test the items against. ///The index of the first matching item, or -1 if no items match. public static int FindIndex(this IEnumerable items, Func 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; } ///Finds the index of the first occurrence of an item in an enumerable. ///The enumerable to search. ///The item to find. ///The index of the first matching item, or -1 if the item was not found. public static int IndexOf(this IEnumerable items, T item) { return items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); } 
 myCars.Select((v, i) => new {car = v, index = i}).First(myCondition).index; 

o el ligeramente más corto

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

Simplemente hazlo:

 int index = List.FindIndex(your condition); 

P.ej

 int index = cars.FindIndex(c => c.ID == 150); 
 myCars.TakeWhile(car => !myCondition(car)).Count(); 

¡Funciona! Piénsalo. El índice del primer elemento coincidente es igual al número de elementos (que no coinciden) antes de él.

Tiempo de cuentos

A mí también me desagrada la horrible solución estándar que ya sugirió en su pregunta. Al igual que la respuesta aceptada, opté por un viejo bucle simple aunque con una ligera modificación:

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

Tenga en cuenta que devolverá la cantidad de elementos en lugar de -1 cuando no coincida. Pero ignoremos esta pequeña molestia por el momento. De hecho, la horrible solución estándar se bloquea en ese caso y considero que es mejor devolver un índice fuera de límites .

Lo que sucede ahora es que ReSharper me dice que Loop se puede convertir a LINQ-expression . Si bien la mayoría de las veces la función empeora la legibilidad, esta vez el resultado fue impresionante. Así que felicitaciones a los JetBrains.

Análisis

Pros

  • Conciso
  • Combinable con otros LINQ
  • Evita new objetos anónimos
  • Solo evalúa el enumerable hasta que el predicado coincida por primera vez

Por lo tanto, lo considero óptimo en tiempo y espacio al tiempo que permanezco legible.

Contras

  • No es del todo obvio al principio
  • No devuelve -1 cuando no hay coincidencia

Por supuesto, siempre puedes esconderlo detrás de un método de extensión. Y qué hacer mejor cuando no hay coincidencia depende en gran medida del contexto.

Haré mi contribución aquí … ¿por qué? simplemente porque: p Es una implementación diferente, basada en la extensión Any LINQ y un delegado. Aquí está:

 public static class Extensions { public static int IndexOf( this IEnumerable list, Predicate 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(); } 

Aquí hay una pequeña extensión que acabo de armar.

 public static class PositionsExtension { public static Int32 Position(this IEnumerable source, Func predicate) { return Positions(source, predicate).FirstOrDefault(); } public static IEnumerable Positions(this IEnumerable source, Func 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; } } 

Entonces puedes llamarlo así.

 IEnumerable indicesWhereConditionIsMet = ListItems.Positions(item => item == this); Int32 firstWelcomeMessage ListItems.Position(msg => msg.WelcomeMessage.Contains("Hello")); 

Aquí hay una implementación de la respuesta más votada que devuelve -1 cuando el elemento no se encuentra:

 public static int FindIndex(this IEnumerable items, Func 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; }