query - linq in c# object
¿Cómo obtener el índice utilizando LINQ? (7)
Esta pregunta ya tiene una respuesta aquí:
- Obtener la posición del elemento Lista <> en c # usando las respuestas de LINQ 9
- ¿Cómo obtener el índice de un elemento en un IEnumerable? 11 respuestas
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.