2012-06-11 14 views
7

Sagen, ich habe folgende DatenVerschachtelung mehr (mehr als 2) unregelmäßige Listen mit LINQ

IEnumerable<IEnumerable<int>> items = new IEnumerable<int>[] { 
    new int[] { 1, 2, 3, 4 }, 
    new int[] { 5, 6 }, 
    new int[] { 7, 8, 9 } 
}; 

Was die einfachste Möglichkeit wäre, eine flache Liste mit den Einzelteilen zurück verschachtelt, so würde ich das Ergebnis:

1, 5, 7, 2, 6, 8, 3, 9, 4

Anmerkung: Die Anzahl der inneren Listen nicht zur Laufzeit bekannt.

Antwort

10

Was Sie beschreiben, ist im Wesentlichen ein Transpose Method wo überhängende Teile enthalten sind und das Ergebnis ist flattened. Hier ist mein Versuch:

static IEnumerable<IEnumerable<T>> TransposeOverhanging<T>(
    this IEnumerable<IEnumerable<T>> source) 
{ 
    var enumerators = source.Select(e => e.GetEnumerator()).ToArray(); 
    try 
    { 
     T[] g; 
     do 
     { 
      yield return g = enumerators 
       .Where(e => e.MoveNext()).Select(e => e.Current).ToArray(); 
     } 
     while (g.Any()); 
    } 
    finally 
    { 
     Array.ForEach(enumerators, e => e.Dispose()); 
    } 
} 

Beispiel:

var result = items.TransposeOverhanging().SelectMany(g => g).ToList(); 
// result == { 1, 5, 7, 2, 6, 8, 3, 9, 4 } 
+0

Loved Ihre Lösung. Denken Sie, das war die magische Linie var enumerators = source.Select (e => e.GetEnumerator()). ToArray(); +1 – Anand

+0

Arbeitete ein Vergnügen, und fügte meinem Toolbelt eine neue Erweiterungsmethode hinzu. Vielen Dank! –

-1

Obwohl seine nicht so elegant wie "DTB" 's Antwort, aber es funktioniert auch und es ist ein einzelner Einsatz :)

Enumerable.Range(0, items.Max(x => x.Count())) 
         .ToList() 
         .ForEach(x => 
            { 
              items 
              .Where(lstChosen => lstChosen.Count()-1 >= x) 
              .Select(lstElm => lstElm.ElementAt(x)) 
              .ToList().ForEach(z => Console.WriteLine(z)); 
            }); 
1

Hier ist mein Versuch, basierend auf dtb's answer. Es vermeidet die externen und internen ToArray Anrufe.

public static IEnumerable<T> Interleave<T>(this IEnumerable<IEnumerable<T>> source) 
{ 
    var enumerators = source.Select(e => e.GetEnumerator()).ToArray(); 
    try 
    { 
     bool itemsRemaining; 
     do 
     { 
      itemsRemaining = false; 
      foreach (var item in 
        enumerators.Where(e => e.MoveNext()).Select(e => e.Current)) 
      { 
       yield return item; 
       itemsRemaining = true; 
      } 
     } 
     while (itemsRemaining); 
    } 
    finally 
    { 
     Array.ForEach(enumerators, e => e.Dispose()); 
    } 
} 
2

Die folgende Lösung ist sehr einfach. Wie sich herausstellt, ist es auch fast doppelt so schnell wie die von dtb vorgeschlagene Lösung.

private static IEnumerable<T> Interleave<T>(this IEnumerable<IEnumerable<T>> source) 
{ 
    var queues = source.Select(x => new Queue<T>(x)).ToList(); 
    while (queues.Any(x => x.Any())) { 
     foreach (var queue in queues.Where(x => x.Any())) { 
      yield return queue.Dequeue(); 
     } 
    } 
} 
0
  • alle Enumeratoren angeordnet ist, selbst dann, wenn Ausnahmen geworfen werden
  • die äußere Sequenz Wertet Spannung, aber lazy evaluation für die inneren Sequenzen verwendet.

public static IEnumerable<T> Interleave<T>(IEnumerable<IEnumerable<T>> sequences) 
{ 
    var enumerators = new List<IEnumerator<T>>(); 
    try 
    { 
     // using foreach here ensures that `enumerators` contains all already obtained enumerators, in case of an expection is thrown here. 
     // this ensures proper disposing in the end 
     foreach(var enumerable in sequences) 
     { 
      enumerators.Add(enumerable.GetEnumerator()); 
     } 

     var queue = new Queue<IEnumerator<T>>(enumerators); 
     while (queue.Any()) 
     { 
      var enumerator = queue.Dequeue(); 
      if (enumerator.MoveNext()) 
      { 
       queue.Enqueue(enumerator); 
       yield return enumerator.Current; 
      } 
     } 
    } 
    finally 
    { 
     foreach(var enumerator in enumerators) 
     { 
      enumerator.Dispose(); 
     } 
    } 
} 
Verwandte Themen