sintaxis query for c# .net linq c#-3.0

query - linq in c# object



¿Cómo obtener el índice utilizando LINQ? (7)

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á el mejor para resolver esto con un viejo bucle simple?


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

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; }


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

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; } }

Entonces puedes llamarlo así.

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


Simplemente haz:

int index = List.FindIndex(your condition);

P.ej

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


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

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

Sin embargo, puedes escribir uno tú mismo:

///<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)); }


Voy a hacer mi contribución aquí ... ¿por qué? solo 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<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(); }


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

o un poco más corto

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


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

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

Tiempo de cuentos

También me disgusta la horrible solución estándar que ya sugirió en su pregunta. Al igual que la respuesta aceptada, opté por un bucle antiguo, aunque con una ligera modificación:

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; }

Tenga en cuenta que devolverá el número de elementos en lugar de -1 cuando no haya coincidencia. Pero ignoremos esta pequeña molestia por ahora. De hecho, la horrible solución estándar falla en ese caso y considero que devolver un índice fuera de los límites es superior .

Lo que sucede ahora es que ReSharper me dice que el bucle se puede convertir en una expresión LINQ . 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 otro LINQ
  • Evita new objetos anónimos.
  • Solo evalúa lo enumerable hasta que el predicado coincida por primera vez.

Por lo tanto, lo considero óptimo en el tiempo y en el espacio mientras sigo legible

Contras

  • No es bastante obvio al principio
  • No devuelve -1 cuando no hay partido.

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