2009-08-17 30 views
105

me escribió esto:¿Cómo obtener el índice de un elemento en un IEnumerable?

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

Pero no sé si ya existe, ¿verdad?

+4

El problema con un enfoque 'Max' es que una: se sigue buscando, y b: devuelve el ** último ** índice cuando hay duplicados (la gente suele esperar el primer índice) –

+1

[geekswithblogs.net] (http://geekswithblogs.net/BlackRabbitCoder/archive/2011/01/06/c. net-ndash-finding-an-itemrsquos-index-in-ienumerablelttgt.aspx) compara 4 soluciones y su rendimiento. El truco 'ToList()/FindIndex()' funciona mejor – nixda

Respuesta

41

El objetivo de hacer las cosas como IEnumerable es para poder iterar perezosamente sobre los contenidos. Como tal, no hay realmente un concepto de índice. Lo que estás haciendo realmente no tiene mucho sentido para un IEnumerable. Si necesita algo que admita el acceso por índice, colóquelo en una lista o colección real.

+5

Actualmente encontré este hilo porque estoy implementando un contenedor IList <> genérico para el tipo IEnumerable <> para usar mis objetos IEnumerable <> con componentes de terceros que solo admiten fuentes de datos de tipo IList. Acepto que tratar de obtener un índice de un elemento dentro de un objeto IEnumerable probablemente sea en la mayoría de los casos un signo de que algo se ha hecho mal, hay momentos en que encontrar ese índice una vez reproduce una gran colección en la memoria solo por encontrar el índice de un solo elemento cuando ya tiene un IEnumerable. – jpierson

+154

-1 causa: hay razones legítimas por las que desea obtener un índice de un 'IEnumerable <>'. No me compro todo el dogma "deberías estar haciendo esto". – ja72

+57

De acuerdo con @ ja72; si no debería tratar con índices con 'IEnumerable', entonces' Enumerable.ElementAt' no existiría. 'IndexOf' es simplemente lo inverso: cualquier argumento en contra de esto debe aplicarse igualmente a' ElementAt'. –

85

me pregunta la sabiduría, pero tal vez:

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

(usando EqualityComparer<T>.Default emular != si es necesario) - pero hay que ver para devolver -1 si no se encuentra ... así que quizás sólo lo hacen es el camino más largo

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

+1 para "cuestionar la sabiduría". 9 veces de cada 10 probablemente sea una mala idea en primer lugar. –

+0

La solución explícita de bucle también se ejecuta 2 veces más rápido (en el peor de los casos) que la solución Select(). Max() también. –

+0

Puede simplemente contar elementos por lambda sin TakeWhile; guarda un bucle: source.Count (x => x! = Value); – Kamarey

19

que implementaría esta manera:

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

¡De hecho, es muy lindo, +1! Implica objetos adicionales, pero deben ser * relativamente * baratos (GEN0), por lo que no es un gran problema. El '==' podría necesitar trabajo? –

+1

Sobrecarga de IEqualityComparer agregada, en verdadero estilo LINQ. ;) – dahlbyk

+1

Creo que quieres decir ... comparer.Equals (x.a, value) =) – Marc

5

creo que la mejor opción es poner en práctica la siguiente manera:

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

sino que también no crear el objeto anónimo

1

Una alternativa a la búsqueda del índice después del hecho es envolver el Enumerable, un tanto similar al uso del método Linq GroupBy().

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

que da un caso de uso de:

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

gran línea de base. También es útil agregar miembros IsEven, IsOdd, IsFirst e IsLast. – JJS

12

La forma en que actualmente estoy haciendo esto es un poco más corto que los que ya sugerida y por lo que yo puedo decir da el resultado deseado:

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

Es un poco torpe, pero cumple su función y es bastante conciso.

+1

Aunque esto funcionaría para colecciones pequeñas, supongamos que tiene un millón de elementos en el "pajar". Hacer un ToList() sobre eso iterará a través de todos los elementos de un millón y los agregará a una lista. Luego buscará en la lista para encontrar el índice del elemento coincidente. Esto sería extremadamente ineficiente, así como la posibilidad de lanzar una excepción si la lista es demasiado grande. – esteuart

+2

@esteuart Definitivamente, debe elegir un enfoque que se adapte a su caso de uso. Dudo que haya una solución única para todos, que posiblemente sea la razón por la cual no existe una implementación en las bibliotecas centrales. –

5

Un poco tarde en el juego, lo sé ... pero esto es lo que hice recientemente. Es ligeramente diferente a la suya, pero le permite al programador dictar lo que la operación de igualdad necesita ser (predicado). Lo cual me parece muy útil cuando trato con diferentes tipos, ya que entonces tengo una forma genérica de hacerlo, independientemente del tipo de objeto y <T> integrado en el operador de igualdad.

También tiene una huella de memoria muy pequeña, y es muy, muy rápido/eficiente ... si te importa eso.

En el peor de los casos, solo tendrá que agregar esto a su lista de extensiones.

De todos modos ... aquí está.

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

Espero que esto ayude a alguien.

+0

Tal vez me esté perdiendo algo, pero ¿por qué 'GetEnumerator' y' MoveNext' en lugar de solo 'foreach'? –

+1

¿Respuesta corta? Eficiencia. Respuesta larga: http://msdn.microsoft.com/en-us/library/9yb8xew9.aspx – MaxOvrdrv

+1

Al observar el IL, parece que la diferencia de rendimiento es que un 'foreach' llamará' Dispose' al enumerador si implementa 'IDisposable'. (Consulte http://stackoverflow.com/questions/4982396/does-foreach-automatically-call-dispose) Como el código en esta respuesta no sabe si el resultado de llamar a 'GetEnumerator' es o no es descartable, debería hacer lo mismo. En ese momento todavía no estoy seguro de que haya un beneficio de performación, ¡aunque hubo IL adicionales cuyo propósito no se me escapó! –

4

unos años más tarde, pero esto utiliza LINQ, devuelve -1 si no se encuentra, no crea objetos adicionales, y debe corto circuito cuando se encontró [a diferencia de la iteración en todo el 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); 
} 

Dónde FirstOr 'es:

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

la mejor manera de coger la posición es por FindIndex Esta función sólo está disponible para List<>

Ejemplo

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

Si tiene empadronador o matriz utilizan esta forma

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

o

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

Esto puede ser realmente fresco con una extensión (que funciona como un proxy), por ejemplo:

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

que asignará automágicamente índices a la colección accesible a través de esta propiedad Index.

Interfaz:

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

extensión personalizada (probablemente más útil para trabajar con EF y 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(); 
0

encontré con este hoy en día en la búsqueda de respuestas y pensé que me gustaría añadir mi versión a la lista (sin juego de palabras). Se utlises el operador nulo condicional de C# 6,0

IEnumerable<Item> collection = GetTheCollection(); 

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

he hecho algunas 'carreras de caballos de los viejos' (prueba) y para grandes colecciones (~ 100.000), peor de los casos que elemento que desea se encuentra en al final, esto es 2x más rápido que haciendo ToList().FindIndex(). Si el artículo que desea está en el medio es ~ 4x más rápido.

Para las colecciones más pequeñas (~ 10.000) parece ser sólo ligeramente más rápido

He aquí cómo lo probé https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e

Cuestiones relacionadas