2009-12-23 27 views
108

Esto se utiliza para general conocimiento:Iterar dos listas o matrices con un comunicado de una ParaCada en C#

Si tengo dos, digamos, Lista, y quiero repetir ambos con el mismo bucle foreach, podemos ¿Haz eso?

Editar

Solo para aclarar, lo que quería hacer esto:

List<String> listA = new List<string> { "string", "string" }; 
List<String> listB = new List<string> { "string", "string" }; 

for(int i = 0; i < listA.Count; i++) 
    listB[i] = listA[i]; 

Pero con un foreach =)

+7

El importante la palabra aquí es "zip". –

+3

¿Desea iterar dos listas _en paralelo_? ¿O desea iterar primero una lista, y luego la otra (con una sola declaración)? –

+0

Creo que su camino se ve mejor que zip – Alexander

Respuesta

189

Esto se conoce como una operación Zip y contará con el apoyo de .NET 4.

Con eso, usted podría escribir algo como:

var numbers = new [] { 1, 2, 3, 4 }; 
var words = new [] { "one", "two", "three", "four" }; 

var numbersAndWords = numbers.Zip(words, (n, w) => new { Number = n, Word = w }); 
foreach(var nw in numbersAndWords) 
{ 
    Console.WriteLine(nw.Number + nw.Word); 
} 

Como una alternativa al tipo anónimo con los campos mencionados, también se puede ahorrar en los apoyos mediante el uso de una tupla y su ayudante Tuple.Create estática:

foreach (var nw in numbers.Zip(words, Tuple.Create)) 
{ 
    Console.WriteLine(nw.Item1 + nw.Item2); 
} 
+2

Aquí hay un artículo sobre él: http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part- 3-intermezzo-linq-s-new-zip-operator.aspx –

+1

No sabía nada sobre esas operaciones de Zip, haré una pequeña investigación sobre ese tema. Gracias! – Hugo

+3

@Hugo: Es una construcción estándar en la Programación funcional :) –

-1

entiendo/Espero que las listas tengan la misma duración: No, su única apuesta es ir con un viejo estándar simple para bucle.

0

No, tendrías que usar un for-loop para eso.

for (int i = 0; i < lst1.Count; i++) 
{ 
    //lst1[i]... 
    //lst2[i]... 
} 

No se puede hacer algo como

foreach (var objCurrent1 int lst1, var objCurrent2 in lst2) 
{ 
    //... 
} 
+0

¿Qué pasa si tienen recuentos diferentes? –

+0

Entonces un foreach que aceptaría una lista arbitraria de enumerables no funcionaría tan bien, por lo tanto, haría que todo fuera inútil. –

10

Puede utilizar Unión o de concatenación, la antigua elimina duplicados, este último no lo hace

foreach (var item in List1.Union(List1)) 
{ 
    //TODO: Real code goes here 
} 

foreach (var item in List1.Concat(List1)) 
{ 
    //TODO: Real code goes here 
} 
+0

Esto no funcionará si son listas de tipos diferentes. –

+0

Otro problema con el uso de un Sindicato es que puede descartar instancias si se evalúan como iguales. Eso no siempre es lo que quieres. –

+1

Creo que su intención era usar colecciones del mismo tipo, – albertein

3

He aquí un IEnumerable encargo < > método de extensión que se puede usar para recorrer dos listas al mismo tiempo.

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace ConsoleApplication1 
{ 
    public static class LinqCombinedSort 
    { 
     public static void Test() 
     { 
      var a = new[] {'a', 'b', 'c', 'd', 'e', 'f'}; 
      var b = new[] {3, 2, 1, 6, 5, 4}; 

      var sorted = from ab in a.Combine(b) 
         orderby ab.Second 
         select ab.First; 

      foreach(char c in sorted) 
      { 
       Console.WriteLine(c); 
      } 
     } 

     public static IEnumerable<Pair<TFirst, TSecond>> Combine<TFirst, TSecond>(this IEnumerable<TFirst> s1, IEnumerable<TSecond> s2) 
     { 
      using (var e1 = s1.GetEnumerator()) 
      using (var e2 = s2.GetEnumerator()) 
      { 
       while (e1.MoveNext() && e2.MoveNext()) 
       { 
        yield return new Pair<TFirst, TSecond>(e1.Current, e2.Current); 
       } 
      } 

     } 


    } 
    public class Pair<TFirst, TSecond> 
    { 
     private readonly TFirst _first; 
     private readonly TSecond _second; 
     private int _hashCode; 

     public Pair(TFirst first, TSecond second) 
     { 
      _first = first; 
      _second = second; 
     } 

     public TFirst First 
     { 
      get 
      { 
       return _first; 
      } 
     } 

     public TSecond Second 
     { 
      get 
      { 
       return _second; 
      } 
     } 

     public override int GetHashCode() 
     { 
      if (_hashCode == 0) 
      { 
       _hashCode = (ReferenceEquals(_first, null) ? 213 : _first.GetHashCode())*37 + 
          (ReferenceEquals(_second, null) ? 213 : _second.GetHashCode()); 
      } 
      return _hashCode; 
     } 

     public override bool Equals(object obj) 
     { 
      var other = obj as Pair<TFirst, TSecond>; 
      if (other == null) 
      { 
       return false; 
      } 
      return Equals(_first, other._first) && Equals(_second, other._second); 
     } 
    } 

} 
0

Si desea un elemento con el correspondiente que podría hacer

Enumerable.Range(0, List1.Count).All(x => List1[x] == List2[x]); 

que devolverá verdadero si cada elemento es igual a la correspondiente a la segunda lista

Si eso es casi pero no exactamente lo que quieres, sería útil si elaborases más.

0

Este método funcionaría para una implementación de lista y podría implementarse como un método de extensión.

public void TestMethod() 
{ 
    var first = new List<int> {1, 2, 3, 4, 5}; 
    var second = new List<string> {"One", "Two", "Three", "Four", "Five"}; 

    foreach(var value in this.Zip(first, second, (x, y) => new {Number = x, Text = y})) 
    { 
     Console.WriteLine("{0} - {1}",value.Number, value.Text); 
    } 
} 

public IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(List<TFirst> first, List<TSecond> second, Func<TFirst, TSecond, TResult> selector) 
{ 
    if (first.Count != second.Count) 
     throw new Exception(); 

    for(var i = 0; i < first.Count; i++) 
    { 
     yield return selector.Invoke(first[i], second[i]); 
    } 
} 
13

Si no quiere esperar a .NET 4.0, podría implementar su propio método Zip. Lo siguiente funciona con .NET 2.0. Puede ajustar la implementación dependiendo de cómo desee manejar el caso donde las dos enumeraciones (o listas) tienen longitudes diferentes: esta continúa hasta el final de la enumeración más larga, y devuelve los valores predeterminados para los elementos faltantes de la enumeración más corta.

static IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> first, IEnumerable<U> second) 
    { 
     IEnumerator<T> firstEnumerator = first.GetEnumerator(); 
     IEnumerator<U> secondEnumerator = second.GetEnumerator(); 

     while (firstEnumerator.MoveNext()) 
     { 
      if (secondEnumerator.MoveNext()) 
      { 
       yield return new KeyValuePair<T, U>(firstEnumerator.Current, secondEnumerator.Current); 
      } 
      else 
      { 
       yield return new KeyValuePair<T, U>(firstEnumerator.Current, default(U)); 
      } 
     } 
     while (secondEnumerator.MoveNext()) 
     { 
      yield return new KeyValuePair<T, U>(default(T), secondEnumerator.Current); 
     } 
    } 

    static void Test() 
    { 
     IList<string> names = new string[] { "one", "two", "three" }; 
     IList<int> ids = new int[] { 1, 2, 3, 4 }; 

     foreach (KeyValuePair<string, int> keyValuePair in ParallelEnumerate(names, ids)) 
     { 
      Console.WriteLine(keyValuePair.Key ?? "<null>" + " - " + keyValuePair.Value.ToString()); 
     } 
    } 
+1

¡Buen método! :). Puede hacer algunos ajustes para usar la misma firma que el método .NET 4 Zip http://msdn.microsoft.com/en-us/library/dd267698.aspx y devolver resultSelector (primero, segundo) en lugar de un KVP. –

+0

Tenga en cuenta que este método no elimina sus enumeradores, lo que podría convertirse en un problema, p. si se usa con enumerables sobre las líneas de archivos abiertos. – Lii

1

También puede simplemente usar una variable entera local si las listas tienen la misma longitud:

List<classA> listA = fillListA(); 
List<classB> listB = fillListB(); 

var i = 0; 
foreach(var itemA in listA) 
{ 
    Console.WriteLine(itemA + listB[i++]); 
} 
0

Puesto que C# 7, se puede usar tuplas ...

int[] nums = { 1, 2, 3, 4 }; 
string[] words = { "one", "two", "three", "four" }; 

foreach (var tuple in nums.Zip(words, (x, y) => (x, y))) 
{ 
    Console.WriteLine($"{tuple.Item1}: {tuple.Item2}"); 
} 

// or... 
foreach (var tuple in nums.Zip(words, (x, y) => (Num: x, Word: y))) 
{ 
    Console.WriteLine($"{tuple.Num}: {tuple.Word}"); 
} 
Cuestiones relacionadas