475 Stimmen

Liste in Unterlisten aufteilen mit LINQ

Gibt es eine Möglichkeit, wie ich eine List<SomeObject> in mehrere separate Listen von SomeObject und dabei den Artikelindex als Trennzeichen für jeden Split?

Lassen Sie mich ein Beispiel geben:

Ich habe eine List<SomeObject> und ich brauche eine List<List<SomeObject>> o List<SomeObject>[] , so dass jede dieser resultierenden Listen eine Gruppe von 3 Elementen der ursprünglichen Liste (in der Reihenfolge) enthält.

z. B.:

  • Ursprüngliche Liste: [a, g, e, w, p, s, q, f, x, y, i, m, c]

  • Daraus resultierende Listen: [a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]

Außerdem muss die Größe der resultierenden Liste ein Parameter dieser Funktion sein.

60voto

3dGrabber Punkte 4250

Ok, hier ist meine Meinung dazu:

  • völlig faul: funktioniert bei unendlichen Aufzählungen
  • kein zwischenzeitliches Kopieren/Puffern
  • O(n) Ausführungszeit
  • funktioniert auch, wenn die inneren Sequenzen nur teilweise verbraucht werden

    public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable, int chunkSize) { if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");

    using (var e = enumerable.GetEnumerator())
    while (e.MoveNext())
    {
        var remaining = chunkSize;    // elements remaining in the current chunk
        var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());
    
        yield return e.GetChunk(innerMoveNext);
        while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
    }

    }

    private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e, Func<bool> innerMoveNext) { do yield return e.Current; while (innerMoveNext()); }

Beispiel für die Verwendung

var src = new [] {1, 2, 3, 4, 5, 6}; 

var c3 = src.Chunks(3);      // {{1, 2, 3}, {4, 5, 6}}; 
var c4 = src.Chunks(4);      // {{1, 2, 3, 4}, {5, 6}}; 

var sum   = c3.Select(c => c.Sum());    // {6, 15}
var count = c3.Count();                 // 2
var take2 = c3.Select(c => c.Take(2));  // {{1, 2}, {4, 5}}

Erklärungen

Der Code funktioniert durch Verschachtelung von zwei yield basierten Iteratoren.

Der äußere Iterator muss verfolgen, wie viele Elemente der innere (Chunk-)Iterator tatsächlich verbraucht hat. Dies geschieht durch Schließen über remaining con innerMoveNext() . Nicht verbrauchte Elemente eines Chunks werden verworfen, bevor der nächste Chunk vom äußeren Iterator ausgegeben wird. Dies ist notwendig, weil man sonst inkonsistente Ergebnisse erhält, wenn die inneren Aufzählungszeichen nicht (vollständig) verbraucht werden (z.B. c3.Count() würde 6 zurückgeben).

Anmerkung: Die Antwort wurde aktualisiert, um die von @aolszowka aufgezeigten Unzulänglichkeiten zu beheben.

18voto

xtofs Punkte 411

Völlig faul, kein Zählen oder Kopieren:

public static class EnumerableExtensions
{

  public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
  {
     if (len == 0)
        throw new ArgumentNullException();

     var enumer = source.GetEnumerator();
     while (enumer.MoveNext())
     {
        yield return Take(enumer.Current, enumer, len);
     }
  }

  private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
  {
     while (true)
     {
        yield return head;
        if (--len == 0)
           break;
        if (tail.MoveNext())
           head = tail.Current;
        else
           break;
     }
  }
}

14voto

Ich denke, der folgende Vorschlag wäre der schnellste. Ich opfere die Faulheit der Quelle Enumerable für die Fähigkeit, Array.Copy zu verwenden und wissen vor der Zeit die Länge jeder meiner Unterlisten.

public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> items, int size)
{
    T[] array = items as T[] ?? items.ToArray();
    for (int i = 0; i < array.Length; i+=size)
    {
        T[] chunk = new T[Math.Min(size, array.Length - i)];
        Array.Copy(array, i, chunk, 0, chunk.Length);
        yield return chunk;
    }
}

12voto

Kevinoid Punkte 2960

Für alle, die an einer paketierten/gewarteten Lösung interessiert sind, ist die MehrLINQ Bibliothek bietet die Batch Erweiterungsmethode, die Ihrem gewünschten Verhalten entspricht:

IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);

Le site Batch Umsetzung ist vergleichbar mit Cameron MacFarland's Antwort mit dem Zusatz einer Überladung für die Umwandlung des Chunks/Stapels vor der Rückgabe und funktioniert recht gut.

11voto

Cameron MacFarland Punkte 67889

Ich habe vor einigen Jahren eine Clump-Erweiterungsmethode geschrieben. Funktioniert hervorragend und ist die schnellste Implementierung hier :P

/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (size < 1)
        throw new ArgumentOutOfRangeException("size", "size must be greater than 0");

    return ClumpIterator<T>(source, size);
}

private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
    Debug.Assert(source != null, "source is null.");

    T[] items = new T[size];
    int count = 0;
    foreach (var item in source)
    {
        items[count] = item;
        count++;

        if (count == size)
        {
            yield return items;
            items = new T[size];
            count = 0;
        }
    }
    if (count > 0)
    {
        if (count == size)
            yield return items;
        else
        {
            T[] tempItems = new T[count];
            Array.Copy(items, tempItems, count);
            yield return tempItems;
        }
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X