it-swarm.com.de

Liste in Sublisten mit LINQ aufteilen

Gibt es eine Möglichkeit, einen List<SomeObject> in mehrere separate Listen von SomeObject aufzuteilen, wobei der Elementindex als Trennzeichen für jeden Split verwendet wird?

Lassen Sie mich beispielhaft erläutern:

Ich habe einen List<SomeObject> und brauche einen List<List<SomeObject>> oder List<SomeObject>[], so dass jede dieser Ergebnislisten eine Gruppe von 3 Elementen der ursprünglichen Liste enthält (nacheinander).

z.B.:

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

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

Ich möchte auch, dass die Ergebnislistengröße ein Parameter dieser Funktion ist.

344
Felipe Lima

Versuchen Sie den folgenden Code.

public static IList<IList<T>> Split<T>(IList<T> source)
{
    return  source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / 3)
        .Select(x => x.Select(v => v.Value).ToList())
        .ToList();
}

Die Idee ist, die Elemente zunächst nach Indizes zu gruppieren. Die Division durch drei führt dazu, dass sie in Gruppen von 3 gruppiert werden. Konvertieren Sie dann jede Gruppe in eine Liste und IEnumerable von List in List von Lists

338
JaredPar

Diese Frage ist etwas alt, aber ich habe das gerade geschrieben und finde es etwas eleganter als die anderen vorgeschlagenen Lösungen:

/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    while (source.Any())
    {
        yield return source.Take(chunksize);
        source = source.Skip(chunksize);
    }
}
293
CaseyB

Im Allgemeinen funktioniert der von CaseyB vorgeschlagene Ansatz gut. Wenn Sie einen List<T> übergeben, ist es schwierig, dies zu bemängeln. Vielleicht würde ich es ändern:

public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
   var pos = 0; 
   while (source.Skip(pos).Any())
   {
      yield return source.Skip(pos).Take(chunksize);
      pos += chunksize;
   }
}

Dadurch werden massive Anrufketten vermieden. Trotzdem weist dieser Ansatz einen allgemeinen Fehler auf. Es werden zwei Aufzählungen pro Block verwirklicht, um das Problem hervorzuheben, das beim Ausführen ausgeführt wird: 

foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
   Console.WriteLine(item);
}
// wait forever 

Um dies zu überwinden, können wir den Ansatz von Cameron versuchen, der den obigen Test in Bravour bestanden hat, da er nur einmal die Aufzählung durchläuft. 

Das Problem ist, dass es einen anderen Fehler hat. Es materialisiert jedes Element in jedem Block. Das Problem bei diesem Ansatz ist, dass der Arbeitsspeicher hoch ist. 

Um dies zu veranschaulichen, versuchen Sie es mit dem Laufen:

foreach (var item in Enumerable.Range(1, int.MaxValue)
               .Select(x => x + new string('x', 100000))
               .Clump(10000).Skip(100).First())
{
   Console.Write('.');
}
// OutOfMemoryException

Schließlich sollte jede Implementierung in der Lage sein, die Iteration von Chunks außerhalb der Reihenfolge zu handhaben, zum Beispiel:

Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]

Viele höchst optimale Lösungen wie meine erste Revision dieser Antwort scheiterten dort. Dasselbe Problem kann in der optimierten Antwort von casperOne gesehen werden.

Um alle diese Probleme anzugehen, können Sie Folgendes verwenden:

namespace ChunkedEnumerator
{
    public static class Extensions 
    {
        class ChunkedEnumerable<T> : IEnumerable<T>
        {
            class ChildEnumerator : IEnumerator<T>
            {
                ChunkedEnumerable<T> parent;
                int position;
                bool done = false;
                T current;


                public ChildEnumerator(ChunkedEnumerable<T> parent)
                {
                    this.parent = parent;
                    position = -1;
                    parent.wrapper.AddRef();
                }

                public T Current
                {
                    get
                    {
                        if (position == -1 || done)
                        {
                            throw new InvalidOperationException();
                        }
                        return current;

                    }
                }

                public void Dispose()
                {
                    if (!done)
                    {
                        done = true;
                        parent.wrapper.RemoveRef();
                    }
                }

                object System.Collections.IEnumerator.Current
                {
                    get { return Current; }
                }

                public bool MoveNext()
                {
                    position++;

                    if (position + 1 > parent.chunkSize)
                    {
                        done = true;
                    }

                    if (!done)
                    {
                        done = !parent.wrapper.Get(position + parent.start, out current);
                    }

                    return !done;

                }

                public void Reset()
                {
                    // per http://msdn.Microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
                    throw new NotSupportedException();
                }
            }

            EnumeratorWrapper<T> wrapper;
            int chunkSize;
            int start;

            public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
            {
                this.wrapper = wrapper;
                this.chunkSize = chunkSize;
                this.start = start;
            }

            public IEnumerator<T> GetEnumerator()
            {
                return new ChildEnumerator(this);
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

        }

        class EnumeratorWrapper<T>
        {
            public EnumeratorWrapper (IEnumerable<T> source)
            {
                SourceEumerable = source;
            }
            IEnumerable<T> SourceEumerable {get; set;}

            Enumeration currentEnumeration;

            class Enumeration
            {
                public IEnumerator<T> Source { get; set; }
                public int Position { get; set; }
                public bool AtEnd { get; set; }
            }

            public bool Get(int pos, out T item) 
            {

                if (currentEnumeration != null && currentEnumeration.Position > pos)
                {
                    currentEnumeration.Source.Dispose();
                    currentEnumeration = null;
                }

                if (currentEnumeration == null)
                {
                    currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
                }

                item = default(T);
                if (currentEnumeration.AtEnd)
                {
                    return false;
                }

                while(currentEnumeration.Position < pos) 
                {
                    currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
                    currentEnumeration.Position++;

                    if (currentEnumeration.AtEnd) 
                    {
                        return false;
                    }

                }

                item = currentEnumeration.Source.Current;

                return true;
            }

            int refs = 0;

            // needed for dispose semantics 
            public void AddRef()
            {
                refs++;
            }

            public void RemoveRef()
            {
                refs--;
                if (refs == 0 && currentEnumeration != null)
                {
                    var copy = currentEnumeration;
                    currentEnumeration = null;
                    copy.Source.Dispose();
                }
            }
        }

        public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
        {
            if (chunksize < 1) throw new InvalidOperationException();

            var wrapper =  new EnumeratorWrapper<T>(source);

            int currentPos = 0;
            T ignore;
            try
            {
                wrapper.AddRef();
                while (wrapper.Get(currentPos, out ignore))
                {
                    yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
                    currentPos += chunksize;
                }
            }
            finally
            {
                wrapper.RemoveRef();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int i = 10;
            foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
            {
                foreach (var n in group)
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
                if (i-- == 0) break;
            }


            var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();

            foreach (var idx in new [] {3,2,1})
            {
                Console.Write("idx " + idx + " ");
                foreach (var n in stuffs[idx])
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
            }

            /*

10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
             */

            Console.ReadKey();


        }

    }
}

Es gibt auch eine Reihe von Optimierungen, die Sie für die Outter-of-Order-Iteration von Chunks einführen können. 

Welche Methode solltest du wählen? Es hängt völlig von dem Problem ab, das Sie zu lösen versuchen. Wenn Sie sich nicht mit dem ersten Fehler beschäftigen, ist die einfache Antwort unglaublich ansprechend.

Note Wie bei den meisten Methoden ist dies nicht sicher für Multi-Threading. Sachen können seltsam werden, wenn Sie den Thread sicher machen möchten, müssen Sie EnumeratorWrapper ändern.

91
Sam Saffron

Sie könnten eine Anzahl von Abfragen verwenden, die Take und Skip verwenden, aber das würde meiner Meinung nach zu viele Iterationen auf der ursprünglichen Liste hinzufügen.

Ich denke eher, dass Sie einen eigenen Iterator erstellen sollten, wie:

public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
  IEnumerable<T> enumerable, int groupSize)
{
   // The list to return.
   List<T> list = new List<T>(groupSize);

   // Cycle through all of the items.
   foreach (T item in enumerable)
   {
     // Add the item.
     list.Add(item);

     // If the list has the number of elements, return that.
     if (list.Count == groupSize)
     {
       // Return the list.
       yield return list;

       // Set the list to a new list.
       list = new List<T>(groupSize);
     }
   }

   // Return the remainder if there is any,
   if (list.Count != 0)
   {
     // Return the list.
     yield return list;
   }
}

Sie können dies dann aufrufen und es ist LINQ-fähig, sodass Sie andere Vorgänge an den resultierenden Sequenzen ausführen können.


In Anbetracht von Sams Antwort hatte ich das Gefühl, dass es einfacher wäre, dies zu tun, ohne:

  • Wiederholtes Durchlaufen der Liste (was ich ursprünglich nicht getan habe)
  • Materialisierung der Elemente in Gruppen vor dem Freigeben des Chunks (bei großen Elementabschnitten gibt es Speicherprobleme)
  • Der gesamte Code, den Sam gepostet hat

Hier ist ein weiterer Durchlauf, den ich in einer Erweiterungsmethode zu IEnumerable<T> namens Chunk kodifiziert habe:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
    int chunkSize)
{
    // Validate parameters.
    if (source == null) throw new ArgumentNullException("source");
    if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
        "The chunkSize parameter must be a positive value.");

    // Call the internal implementation.
    return source.ChunkInternal(chunkSize);
}

Nichts Überraschendes dort oben, nur grundlegende Fehlerprüfung.

Weiter zu ChunkInternal:

private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
    this IEnumerable<T> source, int chunkSize)
{
    // Validate parameters.
    Debug.Assert(source != null);
    Debug.Assert(chunkSize > 0);

    // Get the enumerator.  Dispose of when done.
    using (IEnumerator<T> enumerator = source.GetEnumerator())
    do
    {
        // Move to the next element.  If there's nothing left
        // then get out.
        if (!enumerator.MoveNext()) yield break;

        // Return the chunked sequence.
        yield return ChunkSequence(enumerator, chunkSize);
    } while (true);
}

Im Grunde erhält es den IEnumerator<T> und durchläuft jedes Element manuell. Es prüft, ob aktuell Elemente aufgelistet werden, die aufgelistet werden sollen. Nachdem jeder Block aufgelistet wurde, bricht er aus, wenn keine Elemente mehr vorhanden sind.

Sobald er feststellt, dass Elemente in der Sequenz vorhanden sind, delegiert er die Verantwortung für die innere IEnumerable<T>-Implementierung an ChunkSequence:

private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator, 
    int chunkSize)
{
    // Validate parameters.
    Debug.Assert(enumerator != null);
    Debug.Assert(chunkSize > 0);

    // The count.
    int count = 0;

    // There is at least one item.  Yield and then continue.
    do
    {
        // Yield the item.
        yield return enumerator.Current;
    } while (++count < chunkSize && enumerator.MoveNext());
}

Da MoveNext bereits für den an ChunkSequence übergebenen IEnumerator<T> aufgerufen wurde, ergibt sich das von Current zurückgegebene Element. Anschließend wird der Zähler erhöht, wobei sichergestellt wird, dass nie mehr als chunkSize-Elemente zurückgegeben werden und zum nächsten Element im verschoben wird Sequenz nach jeder Iteration (aber kurzgeschlossen, wenn die Anzahl der Elemente die Blockgröße überschreitet).

Wenn keine Elemente übrig sind, führt die InternalChunk-Methode einen weiteren Durchlauf in der äußeren Schleife aus. Wenn MoveNext jedoch ein zweites Mal aufgerufen wird, wird immer noch false zurückgegeben. laut Dokumentation (Hervorhebung meines):

Wenn MoveNext das Ende der Auflistung übergibt, lautet der Enumerator Nach dem letzten Element in der Auflistung und MoveNext positioniert gibt falsch zurück Wenn sich der Enumerator an dieser Position befindet, folgt Aufrufe von MoveNext geben auch false zurück, bis Reset aufgerufen wird.

An diesem Punkt bricht die Schleife und die Sequenz der Sequenzen wird beendet.

Dies ist ein einfacher Test:

static void Main()
{
    string s = "agewpsqfxyimc";

    int count = 0;

    // Group by three.
    foreach (IEnumerable<char> g in s.Chunk(3))
    {
        // Print out the group.
        Console.Write("Group: {0} - ", ++count);

        // Print the items.
        foreach (char c in g)
        {
            // Print the item.
            Console.Write(c + ", ");
        }

        // Finish the line.
        Console.WriteLine();
    }
}

Ausgabe:

Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,

Ein wichtiger Hinweis, dies wird not funktionieren, wenn Sie nicht die gesamte untergeordnete Sequenz entleeren oder an einer beliebigen Stelle in der übergeordneten Sequenz brechen. Dies ist eine wichtige Einschränkung, aber wenn Ihr Anwendungsfall darin besteht, dass Sie jedes -Element der Sequenz von Sequenzen verbrauchen, funktioniert dies für Sie.

Außerdem wird es seltsame Dinge tun, wenn Sie mit der Reihenfolge spielen, genau wie Sam's an einem Punkt .

63
casperOne

Ok, hier ist meine Meinung dazu:

  • völlig faul: arbeitet mit unendlichen zahlwerten
  • kein zwischenkopieren/puffern
  • O (n) Ausführungszeit 
  • funktioniert auch, wenn innere Abläufe nur zum Teil 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());
}

Verwendungsbeispiel

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, indem zwei yield-basierte Iteratoren verschachtelt werden.

Der äußere Iterator muss nachverfolgen, wie viele Elemente vom inneren (Chunk) -Iterator effektiv verbraucht wurden. Dies geschieht durch Schließen von remaining mit innerMoveNext(). Nicht verbrauchte Elemente eines Chunks werden verworfen, bevor der nächste Iterator den nächsten Chunk abgibt .. Dies ist erforderlich, da Sie sonst inkonsistente Ergebnisse erhalten, wenn die inneren Aufzählungselemente nicht (vollständig) verbraucht werden (z. B. würde c3.Count() 6 zurückgeben).

Hinweis: Die Antwort wurde aktualisiert, um die von @aolszowka aufgezeigten Mängel zu beheben.

44
3dGrabber

völlig faul, weder zählen noch 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;
     }
  }
}
13
user1583558

Ich denke, der folgende Vorschlag wäre der schnellste. Ich opfere die Faulheit der Quelle Enumerable für die Möglichkeit, Array.Copy zu verwenden und die Länge jeder meiner Unterlisten im Voraus zu kennen.

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

System.Interactive stellt für diesen Zweck Buffer() bereit. Einige schnelle Tests zeigen, dass die Leistung der Lösung von Sam ähnlich ist.

8
dahlbyk

Wir können die Lösung von @ JaredPar verbessern, um eine echte "faule" Bewertung durchzuführen. Wir verwenden eine GroupAdjacentBy -Methode, die Gruppen von aufeinanderfolgenden Elementen mit demselben Schlüssel ergibt:

sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))

Da die Gruppen einzeln ausgegeben werden, arbeitet diese Lösung effizient mit langen oder unendlichen Sequenzen.

8
Colonel Panic

Ich habe vor einigen Jahren eine Clump-Erweiterungsmethode geschrieben. Funktioniert super und ist hier die schnellste Implementierung. : 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;
        }
    }
}
7

Hier ist eine Routine zum Aufteilen von Listen, die ich vor ein paar Monaten geschrieben habe:

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}
6
Amy B

Dies ist eine alte Frage, aber damit bin ich am Ende gelandet. Es zählt die Aufzählung nur einmal auf, erstellt jedoch Listen für jede Partition. Es tritt kein unerwartetes Verhalten auf, wenn ToArray() wie einige Implementierungen aufgerufen wird:

    public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (chunkSize < 1)
        {
            throw new ArgumentException("Invalid chunkSize: " + chunkSize);
        }

        using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
        {
            IList<T> currentChunk = new List<T>();
            while (sourceEnumerator.MoveNext())
            {
                currentChunk.Add(sourceEnumerator.Current);
                if (currentChunk.Count == chunkSize)
                {
                    yield return currentChunk;
                    currentChunk = new List<T>();
                }
            }

            if (currentChunk.Any())
            {
                yield return currentChunk;
            }
        }
    }
5
aolszowka

Ich finde, dass dieser kleine Ausschnitt die Arbeit recht gut macht.

public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
    var offset = 0;

    while (offset < source.Count)
    {
        yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
        offset += chunkSize;
    }
}
5
erlando

Wir haben festgestellt, dass die Lösung von David B am besten funktioniert. Wir haben es jedoch an eine allgemeinere Lösung angepasst:

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();
4
mwjackson

Diese folgende Lösung ist die kompakteste, die ich mit O (n) entwickeln könnte.

public static IEnumerable<T[]> Chunk<T>(IEnumerable<T> source, int chunksize)
{
    var list = source as IList<T> ?? source.ToList();
    for (int start = 0; start < list.Count; start += chunksize)
    {
        T[] chunk = new T[Math.Min(chunksize, list.Count - start)];
        for (int i = 0; i < chunk.Length; i++)
            chunk[i] = list[start + i];

        yield return chunk;
    }
}

Alter Code, aber das ist was ich verwendet habe:

    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        var toReturn = new List<T>(max);
        foreach (var item in source)
        {
            toReturn.Add(item);
            if (toReturn.Count == max)
            {
                yield return toReturn;
                toReturn = new List<T>(max);
            }
        }
        if (toReturn.Any())
        {
            yield return toReturn;
        }
    }
4
Robert McKee

Was ist mit diesem?

var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3

var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
                    .Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
                    .ToList();

Soweit ich weiß, ist GetRange () hinsichtlich der Anzahl der genommenen Elemente linear. Das sollte also gut funktionieren.

3
Roman Pekar

Wenn die Liste vom Typ system.collections.generic ist, können Sie die verfügbare Methode "CopyTo" verwenden, um Elemente Ihres Arrays in andere Sub-Arrays zu kopieren. Sie geben das Startelement und die Anzahl der zu kopierenden Elemente an.

Sie können auch 3 Klone Ihrer ursprünglichen Liste erstellen und "RemoveRange" in jeder Liste verwenden, um die Liste auf die gewünschte Größe zu verkleinern.

Oder erstellen Sie einfach eine Hilfsmethode, um dies für Sie zu tun.

3
Jobo

Es ist eine alte Lösung, aber ich hatte einen anderen Ansatz. Ich verwende Skip, um zum gewünschten Offset zu gelangen, und Take, um die gewünschte Anzahl von Elementen zu extrahieren:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
                                                   int chunkSize)
{
    if (chunkSize <= 0)
        throw new ArgumentOutOfRangeException($"{nameof(chunkSize)} should be > 0");

    var nbChunks = (int)Math.Ceiling((double)source.Count()/chunkSize);

    return Enumerable.Range(0, nbChunks)
                     .Select(chunkNb => source.Skip(chunkNb*chunkSize)
                     .Take(chunkSize));
}
2
Bertrand

Für jeden, der an einer gepackten/gewarteten Lösung interessiert ist, stellt die Bibliothek MoreLINQ die Erweiterungsmethode Batch bereit, die Ihrem angeforderten Verhalten entspricht:

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

Die Batch-Implementierung ist der Antwort von Cameron MacFarland mit einer zusätzlichen Überladung für die Umwandlung des Chunks/Stapels vor der Rückkehr ähnlich und funktioniert recht gut.

1
Kevinoid

Ich gebe nur meine zwei Cent ein. Wenn Sie die Liste "zusammenfassen" möchten (visualisieren Sie von links nach rechts), können Sie Folgendes tun:

 public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
    {
        List<List<T>> result = new List<List<T>>();
        for (int i = 0; i < numberOfBuckets; i++)
        {
            result.Add(new List<T>());
        }

        int count = 0;
        while (count < source.Count())
        {
            var mod = count % numberOfBuckets;
            result[mod].Add(source[count]);
            count++;
        }
        return result;
    }
1
user1883961

Verwenden der modularen Partitionierung:

public IEnumerable<IEnumerable<string>> Split(IEnumerable<string> input, int chunkSize)
{
    var chunks = (int)Math.Ceiling((double)input.Count() / (double)chunkSize);
    return Enumerable.Range(0, chunks).Select(id => input.Where(s => s.GetHashCode() % chunks == id));
}
1
Janosz G.

So performatisch wie die Annäherung von Sam Saffron .

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");

    return BatchImpl(source, size).TakeWhile(x => x.Any());
}

static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
    var values = new List<T>();
    var group = 1;
    var disposed = false;
    var e = source.GetEnumerator();

    try
    {
        while (!disposed)
        {
            yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
            group++;
        }
    }
    finally
    {
        if (!disposed)
            e.Dispose();
    }
}

static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
    var min = (group - 1) * size + 1;
    var max = group * size;
    var hasValue = false;

    while (values.Count < min && e.MoveNext())
    {
        values.Add(e.Current);
    }

    for (var i = min; i <= max; i++)
    {
        if (i <= values.Count)
        {
            hasValue = true;
        }
        else if (hasValue = e.MoveNext())
        {
            values.Add(e.Current);
        }
        else
        {
            dispose();
        }

        if (hasValue)
            yield return values[i - 1];
        else
            yield break;
    }
}

}

0
leandromoh

Eine andere Möglichkeit ist die Verwendung von RxPufferoperator

//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;

var observableBatches = anAnumerable.ToObservable().Buffer(size);

var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();
0
frhack

Ich nahm die primäre Antwort und machte es zu einem IOC -Container, um festzustellen, wo aufgeteilt werden soll. ( Für wen ist es wirklich wichtig, nur 3 Elemente aufzuteilen, indem Sie diesen Beitrag lesen, während Sie nach einer Antwort suchen?

Diese Methode erlaubt es, nach Bedarf jeden Artikel zu teilen.

public static List<List<T>> SplitOn<T>(List<T> main, Func<T, bool> splitOn)
{
    int groupIndex = 0;

    return main.Select( item => new 
                             { 
                               Group = (splitOn.Invoke(item) ? ++groupIndex : groupIndex), 
                               Value = item 
                             })
                .GroupBy( it2 => it2.Group)
                .Select(x => x.Select(v => v.Value).ToList())
                .ToList();
}

Also für das OP wäre der Code

var it = new List<string>()
                       { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };

int index = 0; 
var result = SplitOn(it, (itm) => (index++ % 3) == 0 );
0
ΩmegaMan

Kann mit unendlichen Generatoren arbeiten:

a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
 .Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
 .Where((x, i) => i % 3 == 0)

Demo-Code: https://ideone.com/GKmL7M

using System;
using System.Collections.Generic;
using System.Linq;

public class Test
{
  private static void DoIt(IEnumerable<int> a)
  {
    Console.WriteLine(String.Join(" ", a));

    foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
      Console.WriteLine(String.Join(" ", x));

    Console.WriteLine();
  }

  public static void Main()
  {
    DoIt(new int[] {1});
    DoIt(new int[] {1, 2});
    DoIt(new int[] {1, 2, 3});
    DoIt(new int[] {1, 2, 3, 4});
    DoIt(new int[] {1, 2, 3, 4, 5});
    DoIt(new int[] {1, 2, 3, 4, 5, 6});
  }
}
1

1 2

1 2 3
1 2 3

1 2 3 4
1 2 3

1 2 3 4 5
1 2 3

1 2 3 4 5 6
1 2 3
4 5 6

Eigentlich würde ich aber lieber eine entsprechende Methode ohne linq schreiben.

0
Qwertiy