c# - usada - Cómo iterar a través de dos colecciones de la misma longitud usando un solo foreach
recorrer una lista c# (6)
Sé que esta pregunta se ha hecho muchas veces antes, pero probé las respuestas y no parecen funcionar.
Tengo dos listas de la misma longitud pero no del mismo tipo, y quiero recorrerlas a la vez que la list1[i]
está conectada a la list2[i]
.
P.ej:
Suponiendo que tengo list1 (como List<string>
) y list2 (como List<int>
)
Quiero hacer algo como
foreach( var listitem1, listitem2 in list1, list2)
{
// do stuff
}
es posible?
En lugar de un foreach, ¿por qué no usar un for ()? por ejemplo...
int length = list1.length;
for(int i = 0; i < length; i++)
{
// do stuff with list1[i] and list2[i] here.
}
Esto es posible utilizando el operador .NET 4 LINQ Zip () o utilizando la biblioteca de código abierto MoreLINQ que también proporciona el operador Zip()
para que pueda usarlo en versiones anteriores de .NET.
Ejemplo de MSDN :
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
// The following example concatenates corresponding elements of the
// two input sequences.
var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
foreach (var item in numbersAndWords)
{
Console.WriteLine(item);
}
// OUTPUT:
// 1 one
// 2 two
// 3 three
Enlaces útiles:
- Código fuente de la implementación de MoreLINQ Zip (): MoreLINQ Zip.cs
La respuesta corta es no, no puedes.
La respuesta más larga es que foreach
es azúcar sintáctica: obtiene un iterador de la colección y llama a Next
. Esto no es posible con dos colecciones al mismo tiempo.
Si solo desea tener un solo bucle, puede usar un bucle for
y usar el mismo valor de índice para ambas colecciones.
for(int i = 0; i < collectionsLength; i++)
{
list1[i];
list2[i];
}
Una alternativa es combinar ambas colecciones en una utilizando el operador LINQ Zip (nuevo en .NET 4.0) e iterar sobre el resultado.
Puede envolver los dos IEnumerable <> en la clase auxiliar:
var nums = new []{1, 2, 3};
var strings = new []{"a", "b", "c"};
ForEach(nums, strings).Do((n, s) =>
{
Console.WriteLine(n + " " + s);
});
//-----------------------------
public static TwoForEach<A, B> ForEach<A, B>(IEnumerable<A> a, IEnumerable<B> b)
{
return new TwoForEach<A, B>(a, b);
}
public class TwoForEach<A, B>
{
private IEnumerator<A> a;
private IEnumerator<B> b;
public TwoForEach(IEnumerable<A> a, IEnumerable<B> b)
{
this.a = a.GetEnumerator();
this.b = b.GetEnumerator();
}
public void Do(Action<A, B> action)
{
while (a.MoveNext() && b.MoveNext())
{
action.Invoke(a.Current, b.Current);
}
}
}
Editar - Iterando mientras se posiciona en el mismo índice en ambas colecciones
Si el requisito es moverse a través de ambas colecciones de forma ''sincronizada'', es decir, usar el primer elemento de la primera colección con el primer elemento de la segunda colección, luego el segundo con el segundo, y así sucesivamente, sin necesidad de realizar ningún lado efectuando el código, luego vea la respuesta de @llll y use .Zip()
para proyectar pares de elementos en el mismo índice, hasta que una de las colecciones se quede sin elementos.
Más generalmente
En lugar de foreach
, puede acceder al IEnumerator
desde el IEnumerable
de ambas colecciones utilizando el método GetEnumerator()
y luego llamar a MoveNext()
en la colección cuando necesite pasar al siguiente elemento de esa colección. Esta técnica es común cuando se procesan dos o más flujos ordenados, sin necesidad de materializar los flujos.
var stream1Enumerator = stream1.GetEnumerator();
var stream2Enumerator = stream2.GetEnumerator();
// i.e. Until stream1Enumerator runs out of
while (stream1Enumerator.MoveNext())
{
// Now you can iterate the collections independently
if (moon == ''Blue'')
{
stream2Enumerator.MoveNext();
}
// Do something with stream1Enumerator.Current and stream2Enumerator.Current
}
Como han señalado otros, si las colecciones se materializan y admiten la indexación, como una interfaz de ICollection
, también puede usar el operador de subíndices []
, aunque esto parece bastante torpe en la actualidad:
var smallestUpperBound = Math.Min(collection1.Count, collection2.Count);
for (var index = 0; index < smallestUpperBound; index++)
{
// Do something with collection1[index] and collection2[index]
}
Finalmente, también hay una sobrecarga de Linq .Select()
que proporciona el índice ordinal del elemento devuelto, que también podría ser útil.
por ejemplo, a continuación se emparejarán todos los elementos de collection1
alternativamente con los dos primeros elementos de collection2
:
var alternatePairs = collection1.Select(
(item1, index1) => new
{
Item1 = item1,
Item2 = collection2[index1 % 2]
});
foreach(var tup in list1.Zip(list2, (i1, i2) => Tuple.Create(i1, i2)))
{
var listItem1 = tup.Item1;
var listItem2 = tup.Item2;
/* The "do stuff" from your question goes here */
}
Sin embargo, puede ser tal que gran parte de su "hacer cosas" puede ir en la lambda que aquí crea una tupla, que sería aún mejor.
Si las colecciones son tales que pueden ser iteradas, entonces un bucle for()
es probablemente aún más simple.
Actualización: ahora con el soporte ValueTuple
para ValueTuple
en C # 7.0 podemos usar:
foreach ((var listitem1, var listitem2) in list1.Zip(list2, (i1, i2) => (i1, i2)))
{
/* The "do stuff" from your question goes here */
}