2010-10-21 9 views
7

ho: -Zip N IEnumerable <T> s insieme? Iterate su di loro contemporaneamente?

IEnumerable<IEnumerable<T>> items; 

e mi piacerebbe creare: -

IEnumerable<IEnumerable<T>> results; 

dove la prima voce "risultato" è un IEnumerable del primo elemento di ciascuna delle IEnumerables di "articoli", il secondo elemento in "risultati" è un oggetto IEnumerable del secondo elemento di ciascuno di "articoli", ecc.

Gli elementi IEnumerables non hanno necessariamente le stesse lunghezze. Se alcuni degli elementi IEnumerable negli elementi non hanno un elemento in un particolare indice, mi aspetto che l'IEnumerable corrispondente nei risultati contenga un numero inferiore di elementi.

Ad esempio: -

items = { "1", "2", "3", "4" } , { "a", "b", "c" }; 
results = { "1", "a" } , { "2", "b" }, { "3", "c" }, { "4" }; 

Modifica: altro esempio (richiesto nei commenti): -

items = { "1", "2", "3", "4" } , { "a", "b", "c" }, { "p", "q", "r", "s", "t" }; 
results = { "1", "a", "p" } , { "2", "b", "q" }, { "3", "c", "r" }, { "4", "s" }, { "t" }; 

non so in anticipo quanti sequenze ci sono, né quanti elementi sono in ogni sequenza. Potrei avere 1.000 sequenze con 1.000.000 di elementi in ciascuna, e potrei aver bisogno solo del primo ~ 10, quindi mi piacerebbe usare l'enumerazione (pigra) delle sequenze di sorgenti, se possibile. In particolare, non voglio creare una nuova struttura dati se posso aiutarla.

Esiste un metodo incorporato (simile a IEnumerable.Zip) che può eseguire questa operazione?

C'è un altro modo?

+0

Cosa succede se gli articoli contengono tre sequenze? –

+0

È simile a ["Come eseguire iterazioni su due array contemporaneamente?"] (Http://stackoverflow.com/questions/496704/how-to-iterate-over-two-arrays-at-once) (che copre il caso di N = 2) –

+0

Controlla il blog di Eric Lippert (che si è ampliato da una risposta a una domanda SO) sul calcolo di un prodotto cartesiano su molte sequenze arbitrariamente. http://blogs.msdn.com/b/ericlippert/archive/2010/06/28/computing-a-cartesian-product-with-linq.aspx –

risposta

8

Ora leggermente testato e con smaltimento funzionante.

public static class Extensions 
{ 
    public static IEnumerable<IEnumerable<T>> JaggedPivot<T>(
    this IEnumerable<IEnumerable<T>> source) 
    { 
    List<IEnumerator<T>> originalEnumerators = source 
     .Select(x => x.GetEnumerator()) 
     .ToList(); 

    try 
    { 
     List<IEnumerator<T>> enumerators = originalEnumerators 
     .Where(x => x.MoveNext()).ToList(); 

     while (enumerators.Any()) 
     { 
     List<T> result = enumerators.Select(x => x.Current).ToList(); 
     yield return result; 
     enumerators = enumerators.Where(x => x.MoveNext()).ToList(); 
     } 
    } 
    finally 
    { 
     originalEnumerators.ForEach(x => x.Dispose()); 
    } 
    } 
} 

public class TestExtensions 
{ 
    public void Test1() 
    { 
    IEnumerable<IEnumerable<int>> myInts = new List<IEnumerable<int>>() 
    { 
     Enumerable.Range(1, 20).ToList(), 
     Enumerable.Range(21, 5).ToList(), 
     Enumerable.Range(26, 15).ToList() 
    }; 

    foreach(IEnumerable<int> x in myInts.JaggedPivot().Take(10)) 
    { 
     foreach(int i in x) 
     { 
     Console.Write("{0} ", i); 
     } 
     Console.WriteLine(); 
    } 
    } 
} 
+3

Nota che non stai eliminando nessuno degli iteratori, che –

+0

Grazie! Proveremo. –

+0

Innnnnnestesting.Non sapevo che alla fine sarebbe stato attivato con questi metodi di iteratore quando c'è pigrizia, ma ha senso, perché il chiamante chiamerà Di sputa sul pigro IEnumerator. –

4

E 'ragionevolmente semplice da fare se si può garantire come i risultati stanno per essere utilizzato. Tuttavia, se i risultati potrebbero essere utilizzati in un ordine arbitrario, potrebbe essere necessario bufferizzare tutto. Considerate questo:

var results = MethodToBeImplemented(sequences); 
var iterator = results.GetEnumerator(); 
iterator.MoveNext(); 
var first = iterator.Current; 
iterator.MoveNext(); 
var second = iterator.Current; 
foreach (var x in second) 
{ 
    // Do something 
} 
foreach (var x in first) 
{ 
    // Do something 
} 

Al fine di ottenere le voci "secondo" si dovrà iterare su tutte le sottosequenze, passato primi elementi. Se si desidera che sia valido per iterare sugli articoli in first è o è necessario ricordare gli articoli o essere pronti a rivalutare le sottosequenze.

Allo stesso modo è necessario memorizzare le sottosequenze come valori IEnumerable<T> o rileggere l'intero lotto ogni volta.

Fondamentalmente si tratta di un intero vaso di Pandora che è difficile da fare con eleganza in un modo che funziona piacevolmente per tutte le situazioni :(Se si dispone di una specifica situazione in mente con i vincoli del caso, potremmo essere in grado di aiutare di più .

+0

Grazie! Per fortuna posso garantire che i risultati verranno utilizzati in ordine. Questo allevia la necessità delle chiamate a "ToList" nella risposta di DavidB? (Andando a testare ora) –

+0

@Iain: Sospetto di sì, anche se può essere abbastanza complesso farlo bene. Dovrai comunque memorizzare temporaneamente i riferimenti di sottosuccessione come "IEnumerable ". Hmmm. Invece di restituire un 'IEnumerable >', potresti passare un'azione da eseguire su ciascun valore, o qualcosa del genere? Sarebbe molto più semplice! –

+0

Non penso di poter passare una azione (buona idea però, terrò a mente per problemi simili in futuro!). A questo punto, sto pensando che se renderlo elegante lo renderà super-complicato, andrò avanti e incoraggerò tutto. –

0

Ecco uno che è un po 'più breve, ma senza dubbio meno efficiente:

Enumerable.Range(0,items.Select(x => x.Count()).Max()) 
    .Select(x => items.SelectMany(y => y.Skip(x).Take(1))); 
+0

Analogamente alla risposta di AS-CII, ciò chiama troppo spesso GetEnumerator() sul codice sorgente. –

0

che dire di questo?

 List<string[]> items = new List<string[]>() 
     { 
      new string[] { "a", "b", "c" }, 
      new string[] { "1", "2", "3" }, 
      new string[] { "x", "y" }, 
      new string[] { "y", "z", "w" } 
     }; 

     var x = from i in Enumerable.Range(0, items.Max(a => a.Length)) 
       select from z in items 
         where z.Length > i 
         select z[i]; 
+0

Se avessi i dati in memoria già in una forma che potrei fare in modo casuale su di esso, sarebbe così facile. Sfortunatamente ho bisogno dell'enumerazione pigra della sorgente IEnumerables. Chiamare z [i] - o z.ElementAt (i) - lo mina. –

0

Si potrebbe comporre operatori esistenti come questo,

IEnumerable<IEnumerable<int>> myInts = new List<IEnumerable<int>>() 
    { 
     Enumerable.Range(1, 20).ToList(), 
     Enumerable.Range(21, 5).ToList(), 
     Enumerable.Range(26, 15).ToList() 
    }; 

myInts.SelectMany(item => item.Select((number, index) => Tuple.Create(index, number))) 
     .GroupBy(item => item.Item1) 
     .Select(group => group.Select(tuple => tuple.Item2)); 
1

Sulla base di David B's answer, questo codice dovrebbe funzionare meglio:

public static IEnumerable<IEnumerable<T>> JaggedPivot<T>(
    this IEnumerable<IEnumerable<T>> source) 
{ 
    var originalEnumerators = source.Select(x => x.GetEnumerator()).ToList(); 
    try 
    { 
     var enumerators = 
      new List<IEnumerator<T>>(originalEnumerators.Where(x => x.MoveNext())); 

     while (enumerators.Any()) 
     { 
      yield return enumerators.Select(x => x.Current).ToList(); 
      enumerators.RemoveAll(x => !x.MoveNext()); 
     } 
    } 
    finally 
    { 
     originalEnumerators.ForEach(x => x.Dispose()); 
    } 
} 

La differenza è che la variabile enumeratori non è ri -creato tutto il tempo.

Problemi correlati