2008-10-28 32 views
17

Tengo dos listas que son de la misma longitud, ¿es posible recorrer estas dos listas a la vez?Looping a través de 2 listas a la vez

Busco la sintaxis correcta de hacer el siguiente

foreach itemA, itemB in ListA, ListB 
{ 
    Console.WriteLine(itemA.ToString()+","+itemB.ToString()); 
} 

cree usted que esto es posible en C#? Y si lo es, ¿cuál es la expresión lambda equivalente a esto?

+0

¿Desea concatenar los elementos correspondientes de cada lista? ¿O quieres el producto cartesiano de las listas? –

+0

@Vincent: ya que él menciona la misma longitud, supongo que la primera. –

+0

Ninguno. Pero esto no debería importar, ¿verdad? – Graviton

Respuesta

24

[editar]: aclarar; esto es útil en el LINQ genérico/IEnumerable<T> contexto, en el que no se puede utilizar un indexador, debido a: que no existe en un enumerable, yb: no se puede garantizar que se pueden leer los datos de más de una vez. Dado que OP menciona lambdas, ocurre que LINQ podría no estar muy lejos (y sí, me doy cuenta de que LINQ y lambdas no son exactamente lo mismo).

Suena como que necesita el Zip falta operador; puede que la simulan:

static void Main() 
{ 
    int[] left = { 1, 2, 3, 4, 5 }; 
    string[] right = { "abc", "def", "ghi", "jkl", "mno" }; 

    // using KeyValuePair<,> approach 
    foreach (var item in left.Zip(right)) 
    { 
     Console.WriteLine("{0}/{1}", item.Key, item.Value); 
    } 

    // using projection approach 
    foreach (string item in left.Zip(right, 
     (x,y) => string.Format("{0}/{1}", x, y))) 
    { 
     Console.WriteLine(item); 
    } 
} 

// library code; written once and stuffed away in a util assembly... 

// returns each pais as a KeyValuePair<,> 
static IEnumerable<KeyValuePair<TLeft,TRight>> Zip<TLeft, TRight>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right) 
{ 
    return Zip(left, right, (x, y) => new KeyValuePair<TLeft, TRight>(x, y)); 
} 

// accepts a projection from the caller for each pair 
static IEnumerable<TResult> Zip<TLeft, TRight, TResult>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right, 
    Func<TLeft, TRight, TResult> selector) 
{ 
    using(IEnumerator<TLeft> leftE = left.GetEnumerator()) 
    using (IEnumerator<TRight> rightE = right.GetEnumerator()) 
    { 
     while (leftE.MoveNext() && rightE.MoveNext()) 
     { 
      yield return selector(leftE.Current, rightE.Current); 
     } 
    } 
} 
+0

He editado para agregar contexto ... –

+0

Bien, pero ¿hay alguna razón para convertirlo en un método de extensión? – peterchen

+0

lo hace un poco más disponible, es decir, pulse "." y parece, a diferencia de tener que saber sobre SomeUtilityClass.Zip (...); también se adapta muy bien con los otros métodos de extensión para IEnumerable , por lo que me siento cómodo con él como método de extensión. –

11

que va a ser mucho más simple de sólo lo hacen en un viejo y simple bucle for lugar ...

for(int i=0; i<ListA.Length; i++) 
{ 
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString()); 
} 
+0

Sí, no es necesario cambiar cuatro líneas de código para 13. –

+4

La diferencia es que solo necesita el método de extensión Zip * una vez * y puede reutilizarlo para siempre. Además, funcionará en cualquier secuencia en lugar de solo listas. –

+4

Me he sentido avergonzado por el Jon Skeet :( – jcelgin

5

Puede hacerlo explícito.

IEnumerator ListAEnum = ListA.GetEnumerator(); 
IEnumerator ListBEnum = ListB.GetEnumerator(); 

ListBEnum.MoveNext(); 
while(ListAEnum.MoveNext()==true) 
{ 
    itemA=ListAEnum.getCurrent(); 
    itemB=ListBEnum.getCurrent(); 
    Console.WriteLine(itemA.ToString()+","+itemB.ToString()); 
} 

Al menos esto (o algo así) es lo que hace el compilador para un foreach bucle. No obstante, no lo he probado y supongo que faltan algunos parámetros de plantilla para los enumeradores.

Sólo busca GetEnumerator() de la lista y el IEnumerator-Interface.

+0

No ha usado MoveNext en ListBEnum, y definitivamente debería usar "usar"; eche un vistazo al enfoque de Zip, quizás. –

+0

Este no es un mal enfoque, él solo necesita mover el 'ListBEnum.MoveNext' dentro del ciclo while. También es más general que el método Zip. – cdmckay

0

Senthil Kumar's tech blog, tiene una serie que cubre implementaciones de (Python) itertools para C#, incluyendo itertools.izip.

De Itertools for C# - Cycle and Zip, que tienen una solución para cualquier número de iterables (no sólo Lista <T>). Tenga en cuenta que Zip produce un Array en cada iteración:

public static IEnumerable<T[]> Zip<T>(params IEnumerable<T>[] iterables) 
{ 
IEnumerator<T>[] enumerators = Array.ConvertAll(iterables, (iterable) => iterable.GetEnumerator()); 

while (true) 
{ 
    int index = 0; 
    T[] values = new T[enumerators.Length]; 

    foreach (IEnumerator<T> enumerator in enumerators) 
    { 
     if (!enumerator.MoveNext()) 
      yield break; 

     values[index++] = enumerator.Current; 
    } 

    yield return values; 
} 

}

El código obtiene enumeradores para todos los iterables, mueve todos los enumeradores hacia adelante, acumula sus valores actuales en una matriz y se obtiene la matriz. Hace esto hasta que cualquiera de los enumeradores se quede sin elementos.

+0

Buena idea; dos consideraciones sobre la implementación, sin embargo. Primero no elimina los enumeradores (principalmente un problema para excepciones) Pero, lo que es más importante, volver a utilizar la matriz es arriesgado: si llamo a .ToArray() /. ToList() sobre esto, tendré 30 referencias a * la misma matriz *, y no hay forma de obtener los primeros datos. –

+0

Estoy de acuerdo, pero tenga en cuenta que una nueva matriz (podría ser reemplazada por la Lista o alguna otra IEnumerable) se crea para cada rendimiento – gimel

+0

¡Oh, cierto! Mi error. Pensé que el T [] estaba fuera. Código de ceguera. –

1

Recomiendo el uso de old plain for loop, pero debe considerar diferentes longitudes de matriz.Así

for(int i=0; i<ListA.Length; i++) 
{ 
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString()); 
} 

puede convertirse en

for(int i = 0; i < Math.Min(ListA.Length, ListB.Lenght); i++) 
{ 
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString()); 
} 

o incluso en

for(int i = 0; i < Math.Max(ListA.Length, ListB.Lenght); i++) 
    { 
     string valueA = i < ListA.Length ? listA[i].ToString() : ""; 
     string valueB = i < ListB.Length ? listB[i].ToString() : ""; 

     Console.WriteLine(valueA+ ", " + valueB); 
    } 
1

tuve este mismo problema, pero el uso de listas de objetos con listas dentro de ellos .. por lo que su valor , esto podría ayudar a alguien con el mismo problema.

El tiempo de ejecución de esto no es muy bueno ya que IndexOf es O (n), pero en ese momento, estoy lidiando con muchos más bucles foreach internos que en este ejemplo, por lo que no quería para tratar con el manejo de variables de iterador.

En momentos como este echo mucho de menos la notación PHPs foreach ($ arrayList as $ key => $ value) ... tal vez me falta algo en C#, tiene que haber una manera de obtener el índice en O (c) tiempo! (por desgracia este post dice que no: Getting the array key in a 'foreach' loop)

class Stock { 
    string symbol; 
    List<decimal> hourlyPrice; // provides a list of 24 decimals 
} 

// get hourly prices from yesterday and today 
List<Stock> stockMondays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now.AddDay(-1)); 
List<Stock> stockTuesdays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now); 

try { 
    foreach(Stock sMonday in stockMondays) { 
     Stock sTuesday = stockTuesday[stockMondays.IndexOf(sMonday)]; 

     foreach(decimal mondayPrice in sMonday.prices) { 
      decimal tuesdayPrice = sTuesday.prices[sMonday.prices.IndexOf(mondayPrice)]; 
      // do something now 
     } 

    } 
} catch (Exception ex) { // some reason why list counts aren't matching? } 
0

tengo esta pequeña función que me ayuda a iterar a través de estos dos objetos de la lista. schema es del tipo SqlData, que es una clase que contiene tres propiedades. Y los datos son una lista que contiene valores de tipo dinámico. Primero, estoy iterando a través de la colección de esquemas y usando el índice del ítem para iterar a través del objeto de datos.

public List<SqlData> SqlDataBinding(List<SqlData> schema, List<dynamic> data) 
{ 
    foreach (SqlData item in schema) 
    { 
     item.Values = data[schema.IndexOf(item)]; 
    } 
    return schema 
} 
Cuestiones relacionadas