2009-12-23 9 views
108

questo solo per conoscenza generale:Iterate due liste o matrici con dichiarazione di una ForEach in C#

se ho due, diciamo, Lista, e voglio iterare entrambi con lo stesso ciclo foreach, possiamo Fai quello?

Modifica

Giusto per chiarire, ho voluto fare questo:

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

Ma con un foreach =)

+7

L'importante la parola qui è "zip". –

+3

Vuoi ripetere due elenchi _in parallelo_? O vuoi iterare prima una lista e poi l'altra (con una sola istruzione)? –

+0

Penso che la tua strada sia migliore di quella dello zip – Alexander

risposta

189

Questo è noto come operazione Zip e sarà supportato in .NET 4.

Con questo, si sarebbe in grado di scrivere qualcosa come:

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

In alternativa al tipo anonimo con i campi di nome, si può anche risparmiare sulle bretelle utilizzando una tupla e il suo aiutante Tuple.Create statica:

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

Ecco un articolo su di esso: 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

Non sapendo nulla di quelle operazioni Zip, farò una piccola ricerca su quell'argomento. Grazie! – Hugo

+3

@Hugo: È un costrutto standard nella programmazione funzionale :) –

-1

Capisco/speriamo che le liste abbiano la stessa lunghezza: No, la tua unica scommessa è di andare in un semplice vecchio standard per il ciclo.

0

No, dovresti usare un ciclo for per quello.

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

Non si può fare qualcosa di simile

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

Cosa succede se hanno conteggi diversi? –

+0

Quindi un foreach che accetterebbe una lista arbitraria di enumerables non funzionerebbe altrettanto, rendendo così l'intera cosa inutile. –

10

È possibile utilizzare dell'Unione o Concat, l'ex rimuove i duplicati, il più tardi non lo fa

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

Questo non funzionerà se sono elenchi di tipi diversi. –

+0

Un altro problema con l'utilizzo di un'unione è che può gettare via istanze se valutano come uguali. Potrebbe non essere sempre quello che vuoi. –

+1

Ho deciso che la sua intenzione era di utilizzare raccolte dello stesso tipo, – albertein

3

Ecco un IEnumerable personalizzato < > metodo di estensione che può essere utilizzato per scorrere due elenchi contemporaneamente.

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

Se si desidera un elemento con il corrispondente quello che si poteva fare

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

che restituirà vero se ogni elemento è uguale a quella corrispondente al secondo elenco

Se questo è quasi ma non proprio quello che vuoi sarebbe di grande aiuto se tu approfondissi di più.

0

Questo metodo potrebbe funzionare per l'implementazione di un elenco e potrebbe essere implementato come metodo di estensione.

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

Se non si desidera attendere .NET 4.0, è possibile implementare il proprio metodo Zip. Quanto segue funziona con .NET 2.0. È possibile regolare l'implementazione in base a come si desidera gestire il caso in cui le due enumerazioni (o elenchi) hanno lunghezze diverse: questa continua alla fine dell'enumerazione più lunga, restituendo i valori predefiniti per gli elementi mancanti dall'enumerazione più breve.

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

Bel metodo! :). È possibile apportare alcune modifiche per utilizzare la stessa firma del metodo Zip .NET 4 http://msdn.microsoft.com/en-us/library/dd267698.aspx e restituire resultSelector (primo, secondo) invece di un KVP. –

+0

Si noti che questo metodo non dispone i suoi enumeratori che potrebbero diventare un problema, ad es. se è usato con enumerabili sulle linee di file aperti. – Lii

1

Si potrebbe anche semplicemente utilizzare una variabile intera locale se le liste hanno la stessa lunghezza:

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

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

Poiché C# 7, è possibile utilizzare tuple ...

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}"); 
} 
Problemi correlati