2008-10-26 16 views
15

¿Hay una mejor manera de hacerlo?Obteniendo una colección de valores de índice usando una consulta LINQ

string[] s = {"zero", "one", "two", "three", "four", "five"}; 

var x = 
s 
.Select((a,i) => new {Value = a, Index = i}) 
.Where(b => b.Value.StartsWith("t")) 
.Select(c => c.Index); 

es decir, estoy buscando una manera más eficiente o más elegante para obtener las posiciones de los elementos que coinciden con los criterios.

Respuesta

28

Desde aquí se puede añadir su propio método de extensión:

public static IEnumerable<int> IndexesWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate) 
{ 
    int index=0; 
    foreach (T element in source) 
    { 
     if (predicate(element)) 
     { 
      yield return index; 
     } 
     index++; 
    } 
} 

Entonces usarlo con:

string[] s = {"zero", "one", "two", "three", "four", "five"}; 
var x = s.IndexesWhere(t => t.StartsWith("t")); 
5

Me parece bien. Es posible guardar un par de caracteres cambiando la selección para:

.Select((Value, Index) => new {Value, Index}) 
+0

Gracias - No sabía que pudieras hacer eso - Pensé que tenías que reasignar. – Guy

+2

Se llama "inicializador de proyección": básicamente toma la última subexpresión (que debe ser un campo o propiedad) dentro de la expresión y la utiliza para el nombre. Entonces podrías hacer x.GetFoo(). Bar y eso sería equivalente a Bar = x.GetFoo(). Bar. –

6

Si sólo está usando el ejemplo como una manera de aprender LINQ, ignorar este mensaje.


No es claro para mí que LINQ sea realmente la mejor manera de hacerlo. El siguiente código parece ser más eficiente ya que no es necesario crear un nuevo tipo anónimo. Por supuesto, su ejemplo puede ser ideado y la técnica podría ser más útil en un contexto diferente, por ejemplo, en una estructura de datos donde podría aprovechar un índice de valor, pero el código siguiente es razonablemente directo, comprensible (sin pensamiento). requerido) y posiblemente más eficiente.

string[] s = {"zero", "one", "two", "three", "four", "five"}; 
List<int> matchingIndices = new List<int>(); 

for (int i = 0; i < s.Length; ++i) 
{ 
    if (s[i].StartWith("t")) 
    { 
     matchingIndices.Add(i); 
    } 
} 
+0

Gracias por la respuesta. Estoy de acuerdo en que esto sería más eficiente. Como suponía, esta es una versión simplificada de algo más complejo que "debe" hacerse en LINQ en este caso particular. – Guy

+2

Y ese código exacto solo funciona cuando estás usando List en lugar de un IEnumerable arbitrario. De lo contrario, tendrías que usar foreach y una variable local separada. –

1

¿Qué tal esto? Es similar al póster original, pero primero selecciono los índices y luego construyo una colección que coincide con los criterios.

var x = s.Select((a, i) => i).Where(i => s[i].StartsWith("t")); 

Esto es un poco menos eficiente que algunas de las otras respuestas ya que la lista se itera por completo en dos ocasiones.

0

Discutí este problema interesante con un colega y al principio pensé que la solución de JonSkeet era genial, pero mi colega señaló un problema, a saber, que si la función es una extensión de IEnumerable<T>, entonces puede usarse donde una colección implementa eso.

Con una matriz, que es seguro decir que el orden producido con foreach será respetada (es decir foreach se repetirá desde el primero al último), pero no sería necesariamente el caso con otras colecciones (Lista, diccionario, etc.), donde foreach no reflejaría necesariamente "orden de entrada". Sin embargo, la función está ahí, y puede ser engañosa.

Al final, acabé con algo similar a la respuesta de tvanfosson, sino como un método de extensión, para las matrices:

public static int[] GetIndexes<T>(this T[]source, Func<T, bool> predicate) 
{ 
    List<int> matchingIndexes = new List<int>(); 

    for (int i = 0; i < source.Length; ++i) 
    { 
     if (predicate(source[i])) 
     { 
      matchingIndexes.Add(i); 
     } 
    } 
    return matchingIndexes.ToArray(); 
} 

Aquí está la esperanza List.ToArray respetará el orden de la última operación ...

+0

"Sin embargo, la función está ahí, y puede ser engañosa". - pero eso sería lo mismo para cualquiera de los otros métodos LINQ que usan índices, p. el '.Select ((item, index) =>'. No creo que sea necesariamente algo incorrecto con la función de Jon, siempre y cuando comprenda lo que está obteniendo. – Rup

+1

@Rup He reunido más experiencia con LINQ y el Interfaz de IEnumerable desde esa respuesta. Acepto que si sabes lo que estás haciendo, y si se define la función, no habrá sorpresas. Un peligro potencial sería sacar los índices de un IEnumerable producido con 'AsParallel'. Esos índices reflejarán el estado de cómo se produjo la colección, pero producirla de nuevo casi garantiza que dará un resultado diferente. Los índices se referirán a algo que no existe. En el trabajo en equipo (o reutilizando la función más adelante), si eso comportamiento no está claro, vas a estar en problemas. – MPelletier

+0

Todavía creo que es probable que el orden no cambie a menos que modifiques la colección, y que no deberías usar los índices a menos que tenga sentido para la estructura . Pero no lo había considerado d AsParallel - ordenado, gracias. – Rup

Cuestiones relacionadas