2015-12-22 13 views
6

Dato un gruppo di elenchi, ho bisogno di scorrere su di essi contemporaneamente. Supponiamo che ne abbia tre: list1, list2 e list3.Iterare su più elenchi

Quello che ho trovato finora è la seguente:

foreach (var tuple in list1.Zip(list2, (first, second) => new { object1 = first, object2 = second }) 
          .Zip(list3, (first, second) => new { object1 = first.object1, object2 = first.object2, object3 = second })) 
{ 
    //do stuff 
} 

Questo funziona bene ed è abbastanza leggibile, a meno che il numero di liste non è grande. So come estenderlo ulteriormente alle 4, 5, .... liste, ma se ne comprimo 10, il codice sarebbe estremamente lungo. C'è qualche possibilità di refactoring? O avrei bisogno di un'altra soluzione rispetto alla funzione Zip?

+6

uso 'for' e di accesso elenca gli elementi di indice – ASh

+4

Quindi, "allo stesso tempo" non significa in parallelo, in questo caso, giusto? –

+0

Hai 'lista1',' lista2', ... in una collezione? – CodeCaster

risposta

7

Con l'aiuto di un po 'di generazione di codice (si pensi T4), si potrebbe produrre fino a 6 sovraccarichi (perché Tuple è limitata a 7 argomenti generici) di qualcosa di simile a:

public static class Iterate 
{ 
    public static IEnumerable<Tuple<T1, T2, T3>> Over<T1, T2, T3>(IEnumerable<T1> t1s, IEnumerable<T2> t2s, IEnumerable<T3> t3s) 
    { 
     using(var it1s = t1s.GetEnumerator()) 
     using(var it2s = t2s.GetEnumerator()) 
     using(var it3s = t3s.GetEnumerator()) 
     { 
      while(it1s.MoveNext() && it2s.MoveNext() && it3s.MoveNext()) 
       yield return Tuple.Create(it1s.Current, it2s.Current, it3s.Current); 
     } 
    } 
} 

Con questa Iterate classe, iterazione diventa molto semplice:

foreach(var t in Iterate.Over(
    new[] { 1, 2, 3 }, 
    new[] { "a", "b", "c" }, 
    new[] { 1f, 2f, 3f })) 
{ 
} 

Questo può essere futher generalizzata (con una perdita complessiva di sicurezza tipo) a:

public static IEnumerable<object[]> Over(params IEnumerable[] enumerables) 
+0

Funziona come 'enum.Zip' quando le liste non hanno lo stesso numero di elementi? Io non la penso così – B0Andrew

+0

@ B0Andrew Sì, l'iterazione si interrompe non appena viene esaurita la raccolta più corta. –

+0

Il mio errore. Hai ragione. – B0Andrew

4

Perché non il buon vecchio ciclo for?

int n = new int[] { 
    list1.Count, 
    list2.Count, 
    list3.Count, 
    // etc. 
    }.Min(); // if lists have different number of items 

    for (int i = 0; i < n; ++i) { 
    var item1 = list1[i]; // if you want an item 
    ... 
    } 
1

Per quanto ne ho, il vero problema è il numero sconosciuto di elenchi da scorrere. Un altro problema che vedo è che non vi è alcuna garanzia che tutte le liste abbiano la stessa lunghezza ... corretta?

Se il numero delle liste è sconosciuto, tuple non lo farà, perché se ne andranno fino a 8 ... e devono essere impostati in fase di compilazione ...

In tal caso vorrei suggerire che si invece di mappare su una tupla, fallo su una struttura semplice e molto vecchia: una matrice! La larghezza sarà il numero di lista (noto al runtime) e la profondità sarà la lista più lunga. È possibile eseguire l'iterazione utilizzando un semplice e ben noto for, fare in modo che il compilatore ottimizzi la memoria e l'allocazione ... Il codice sarà molto leggibile non solo da C# ma per praticamente chiunque lavori con qualsiasi tipo di linguaggio di programmazione ...

+0

Non ho pensato a questo :) ma non ci vuole troppo memoria? scusa se questa è una domanda stupida, sono un noob :) – katta

0

Aggiungendo a @ di AntonGogolev risposta, sulla sua ultima osservazione ... se non si cura di tipo-sicurezza e prestazioni (per la boxe-unboxing), è possibile implementare un enumeratore utilizzando object[]:

public static class Iterator 
{ 
    public static IEnumerable<object[]> Enumerate(params IEnumerable[] enumerables) 
    { 
    var list = new List<object>(); 
    var enumerators = new List<IEnumerator>(); 
    bool end = false; 

    foreach(var enu in enumerables) 
    { 
     enumerators.Add(enu.GetEnumerator()); 
    } 
    while(!end) 
    { 
     list.Clear(); 
     foreach(var enu in enumerators) 
     { 
      if(!enu.MoveNext()) { end = true; break; } 
      list.Add(enu.Current);     
     } 
     if(!end) yield return list.ToArray();   
    } 
    } 
} 

Attenzione: non è stato fatto alcuno sforzo per ottimizzare questo codice ed è stato scritto come è venuto attraverso le dita :-)

Si può usare come:

var listA = new[] { 1, 2, 3 }; 
var listB = new[] { "a", "b", "c" }; 
var listC = new[] { 5f, 6f, 7f }; 
foreach(var n in Iterator.Enumerate(listA, listB, listC)) 
{ 
    foreach(var obj in n) 
    { 
     Console.Write(obj.ToString() + ", "); 
    } 
    Console.WriteLine(); 
} 

Fiddle qui: https://dotnetfiddle.net/irTY8M

Problemi correlati