Teilen Sie eine Sammlung mit LINQ in `n` Teile?

122

Gibt es eine gute Möglichkeit, eine Sammlung nmit LINQ in Teile zu teilen ? Natürlich nicht unbedingt gleichmäßig.

Das heißt, ich möchte die Sammlung in Untersammlungen unterteilen, die jeweils eine Teilmenge der Elemente enthalten, in denen die letzte Sammlung zerlumpt werden kann.

Simon_Weaver
quelle
1
Retagged: Die Frage hat nichts mit asp.net zu tun. Bitte kennzeichnen Sie Ihre Fragen entsprechend.
Wie genau möchten Sie, dass sie aufgeteilt werden, wenn nicht sogar (natürlich unter Berücksichtigung des Endes)?
Marc Gravell
1
Wer hat mit dieser Frage verlinkt? John warst du es? :-) plötzlich alle diese Antworten :-)
Simon_Weaver
Verwandte Frage Liste in Unterlisten mit LINQ aufteilen
Gennady Vanin Геннадий Ванин
@Simon_Weaver Ich habe versucht zu klären, was Sie fragen, basierend auf der akzeptierten Antwort. Tatsächlich gibt es viele Möglichkeiten, eine Liste zu "teilen", einschließlich der Zerlegung jedes Elements der Liste in seine Elemente und der Einfügung dieser Elemente in sogenannte "parallele" Listen.
Jpaugh

Antworten:

127

Eine reine Linq und die einfachste Lösung ist wie unten gezeigt.

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}
Muhammad Hasan Khan
quelle
3
Sie haben folgende Möglichkeiten: select part.AsEnumerable () anstelle von select (IEnumerable <T>) part. Es fühlt sich eleganter an.
Tuinstoel
2
Das Ausführen all dieser Moduloperationen kann auf langen Listen etwas teuer werden.
Jonathan Allen
8
Es ist besser, die Select-Überladung zu verwenden, die den Index enthält.
Marc Gravell
1
Ich habe eine Antwort hinzugefügt, die die Syntax von Select Overload und Method Chaining verwendet
reustmd
1
.AsEnumerable()ist nicht erforderlich, IGrouping <T> ist bereits eine IEnumerable <T>.
Alex
58

EDIT: Okay, es sieht so aus, als hätte ich die Frage falsch verstanden. Ich las es als "Stücke der Länge n" und nicht als "n Stücke". Doh! Erwägen, die Antwort zu löschen ...

(Ursprüngliche Antwort)

Ich glaube nicht, dass es eine integrierte Partitionierungsmethode gibt, obwohl ich beabsichtige, eine in meine Reihe von Ergänzungen zu LINQ to Objects zu schreiben. Marc Gravell hat hier eine Implementierung, obwohl ich sie wahrscheinlich ändern würde, um eine schreibgeschützte Ansicht zurückzugeben:

public static IEnumerable<IEnumerable<T>> Partition<T>
    (this IEnumerable<T> source, int size)
{
    T[] array = null;
    int count = 0;
    foreach (T item in source)
    {
        if (array == null)
        {
            array = new T[size];
        }
        array[count] = item;
        count++;
        if (count == size)
        {
            yield return new ReadOnlyCollection<T>(array);
            array = null;
            count = 0;
        }
    }
    if (array != null)
    {             
        Array.Resize(ref array, count);
        yield return new ReadOnlyCollection<T>(array);
    }
}
Jon Skeet
quelle
Verdammt - schlagen Sie mich dazu ;-p
Marc Gravell
3
Sie mögen diese "Array [count ++]" wirklich nicht, wie ;-p
Marc Gravell
18
Vielen Dank, dass Sie nicht gelöscht haben, obwohl dies keine Antwort für das OP ist. Ich wollte genau das Gleiche - Stücke der Länge n :).
Gishu
2
@Dejan: Nein, tut es nicht. Beachten Sie die Verwendung von yield return. Es muss jeweils ein Stapel gespeichert sein, aber das ist alles.
Jon Skeet
1
@ Dejan: Richtig - Ich möchte nicht raten, wie es mit paralleler LINQ-Partitionierung interagiert, um ehrlich zu sein :)
Jon Skeet
39
static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
            return list.Select((item, index) => new {index, item})
                       .GroupBy(x => x.index % parts)
                       .Select(x => x.Select(y => y.item));
    }
}
reustmd
quelle
28
Ich habe eine irrationale Abneigung gegen Linq im SQL-Stil, daher ist dies meine Lieblingsantwort.
Piedar
1
@ manu08, ich habe deinen Code ausprobiert, ich habe eine Liste var dept = {1,2,3,4,5}. Nach dem Teilen ist das Ergebnis wie dept1 = {1,3,5}und dept2 = { 2,4 }wo parts = 2. Aber das Ergebnis, das ich brauche, ist dept1 = {1,2,3}unddept2 = {4,5}
Karthik Arthik
3
Ich hatte das gleiche Problem mit Modulo, also habe ich die Spaltenlänge mit berechnet und int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);dann mit geteilt .GroupBy(x => x.index / columnLength). Ein Nachteil ist, dass Count () die Liste auflistet.
Goodeye
24

Ok, ich werfe meinen Hut in den Ring. Die Vorteile meines Algorithmus:

  1. Keine teuren Multiplikations-, Divisions- oder Moduloperatoren
  2. Alle Operationen sind O (1) (siehe Hinweis unten)
  3. Funktioniert für IEnumerable <> source (keine Count-Eigenschaft erforderlich)
  4. Einfach

Der Code:

public static IEnumerable<IEnumerable<T>>
  Section<T>(this IEnumerable<T> source, int length)
{
  if (length <= 0)
    throw new ArgumentOutOfRangeException("length");

  var section = new List<T>(length);

  foreach (var item in source)
  {
    section.Add(item);

    if (section.Count == length)
    {
      yield return section.AsReadOnly();
      section = new List<T>(length);
    }
  }

  if (section.Count > 0)
    yield return section.AsReadOnly();
}

Wie in den Kommentaren unten ausgeführt, geht dieser Ansatz nicht auf die ursprüngliche Frage ein, bei der eine feste Anzahl von Abschnitten mit ungefähr gleicher Länge gefordert wurde. Trotzdem können Sie meinen Ansatz immer noch verwenden, um die ursprüngliche Frage zu lösen, indem Sie sie so nennen:

myEnum.Section(myEnum.Count() / number_of_sections + 1)

Bei dieser Verwendung ist der Ansatz nicht mehr O (1), da die Count () -Operation O (N) ist.

Mike
quelle
Genial - beste Lösung hier! Einige Optimierungen: * Löschen Sie die verknüpfte Liste, anstatt für jeden Abschnitt eine neue zu erstellen. Ein Verweis auf die verknüpfte Liste wird niemals an den Anrufer zurückgegeben, daher ist dies absolut sicher. * Setzen Sie die verknüpfte Liste nicht erstellt werden, bis Sie den ersten Punkt erreichen - so gibt es keine Zuordnung ist , wenn die Quelle leer ist
shadowchaser
3
@ShadowChaser Laut MSDN ist das Löschen der LinkedList O (N) -Komplexität, sodass mein Ziel von O (1) ruiniert wird. Natürlich könnte man argumentieren, dass der foreach zunächst O (N) ist ... :)
Mike
4
Ihre Antwort ist richtig, aber die Frage ist falsch. Ihre Antwort gibt eine unbekannte Anzahl von Blöcken mit fester Größe für jeden Block an. OP möchte jedoch eine Split-Funktionalität, bei der eine feste Anzahl von Blöcken mit einer beliebigen Größe pro Block (hoffentlich gleich oder nahezu gleich groß) angegeben wird. Vielleicht besser geeignet hier stackoverflow.com/questions/3773403/…
nawfal
1
@ Mike hast du es verglichen? Ich hoffe, Sie wissen, dass O (1) nicht schneller bedeutet, sondern nur, dass die für die Partitionierung erforderliche Zeit nicht skaliert. Ich frage mich nur, was Ihr Grund ist, blind bei O (1) zu bleiben, wenn es für alle realen Szenarien langsamer sein kann als andere O (n). Ich habe es sogar auf eine verrückte 10 ^ 8-Stärke getestet und meine schien noch schneller zu sein. Ich hoffe, Sie wissen, dass es nicht einmal Standard-Sammlungstypen gibt, die 10 ^ 12 Artikel aufnehmen können.
Nawfal
1
@nawfal - Vielen Dank für Ihre detaillierte Analyse, es hilft mir, auf Trab zu bleiben. Verknüpfte Listen sind im Allgemeinen für effiziente End-Inserts bekannt, weshalb ich sie hier ausgewählt habe. Ich habe es jedoch nur verglichen und tatsächlich ist die Liste <> viel schneller. Ich vermute, dass dies eine Art Detail der .NET-Implementierung ist, das möglicherweise eine separate StackOverflow-Frage verdient. Ich habe meine Antwort so geändert, dass List <> gemäß Ihrem Vorschlag verwendet wird. Die Vorbelegung der Listenkapazität garantiert, dass das Einfügen am Ende immer noch O (1) ist und mein ursprüngliches Entwurfsziel erfüllt. Ich habe auch auf das in .NET 4.5 integrierte .AsReadOnly () umgestellt.
Mike
16

Dies ist das Gleiche wie die akzeptierte Antwort, jedoch eine viel einfachere Darstellung:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

Das obige Verfahren teilt eine IEnumerable<T>in N Anzahl von Blöcken gleicher Größe oder nahezu gleicher Größe auf.

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

Bei der obigen Methode wird ein IEnumerable<T>in Blöcke mit der gewünschten festen Größe aufgeteilt, wobei die Gesamtzahl der Blöcke unwichtig ist - worum es bei der Frage nicht geht.

Das Problem bei der SplitMethode ist, dass sie nicht nur langsamer ist, sondern auch die Ausgabe in dem Sinne verschlüsselt, dass die Gruppierung auf der Grundlage des i-ten Vielfachen von N für jede Position erfolgt, oder mit anderen Worten, Sie erhalten die Chunks nicht in der ursprünglichen Reihenfolge.

Fast jede Antwort hier bewahrt entweder nicht die Ordnung oder handelt von Partitionierung und nicht von Aufteilung oder ist eindeutig falsch. Versuchen Sie dies, was schneller ist, die Ordnung bewahrt, aber ein bisschen ausführlicher ist:

public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items, 
                                                   int numberOfChunks)
{
    if (numberOfChunks <= 0 || numberOfChunks > items.Count)
        throw new ArgumentOutOfRangeException("numberOfChunks");

    int sizePerPacket = items.Count / numberOfChunks;
    int extra = items.Count % numberOfChunks;

    for (int i = 0; i < numberOfChunks - extra; i++)
        yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);

    int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
    int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
    for (int i = 0; i < extra; i++)
        yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}

Die äquivalente Methode für eine PartitionOperation hier

nawfal
quelle
6

Ich habe die Partitionsfunktion, die ich zuvor gepostet habe, ziemlich oft verwendet. Das einzig schlechte daran war, dass es nicht vollständig gestreamt wurde. Dies ist kein Problem, wenn Sie mit wenigen Elementen in Ihrer Sequenz arbeiten. Ich brauchte eine neue Lösung, als ich anfing, mit mehr als 100.000 Elementen in meiner Sequenz zu arbeiten.

Die folgende Lösung ist viel komplexer (und mehr Code!), Aber sehr effizient.

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

namespace LuvDaSun.Linq
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
        {
            /*
            return enumerable
                .Select((item, index) => new { Item = item, Index = index, })
                .GroupBy(item => item.Index / partitionSize)
                .Select(group => group.Select(item => item.Item)                )
                ;
            */

            return new PartitioningEnumerable<T>(enumerable, partitionSize);
        }

    }


    class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        IEnumerable<T> _enumerable;
        int _partitionSize;
        public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
        {
            _enumerable = enumerable;
            _partitionSize = partitionSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
        }

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


    class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
            _enumerator.Dispose();
        }

        IEnumerable<T> _current;
        public IEnumerable<T> Current
        {
            get { return _current; }
        }
        object IEnumerator.Current
        {
            get { return _current; }
        }

        public void Reset()
        {
            _current = null;
            _enumerator.Reset();
        }

        public bool MoveNext()
        {
            bool result;

            if (_enumerator.MoveNext())
            {
                _current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
                result = true;
            }
            else
            {
                _current = null;
                result = false;
            }

            return result;
        }

    }



    class PartitionEnumerable<T> : IEnumerable<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new PartitionEnumerator<T>(_enumerator, _partitionSize);
        }

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


    class PartitionEnumerator<T> : IEnumerator<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        int _count;
        public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
        }

        public T Current
        {
            get { return _enumerator.Current; }
        }
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }
        public void Reset()
        {
            if (_count > 0) throw new InvalidOperationException();
        }

        public bool MoveNext()
        {
            bool result;

            if (_count < _partitionSize)
            {
                if (_count > 0)
                {
                    result = _enumerator.MoveNext();
                }
                else
                {
                    result = true;
                }
                _count++;
            }
            else
            {
                result = false;
            }

            return result;
        }

    }
}

Genießen!

Elmer
quelle
Diese Version bricht den Vertrag von IEnumerator. Es ist nicht gültig, InvalidOperationException auszulösen, wenn Reset aufgerufen wird. Ich glaube, dass viele der LINQ-Erweiterungsmethoden auf diesem Verhalten beruhen.
ShadowChaser
1
@ShadowChaser Ich denke, Reset () sollte eine NotSupportedException auslösen und alles wäre in Ordnung. Aus der MSDN-Dokumentation: "Die Reset-Methode wird für die COM-Interoperabilität bereitgestellt. Sie muss nicht unbedingt implementiert werden. Stattdessen kann der Implementierer einfach eine NotSupportedException auslösen."
Tong
@toong Wow, du hast recht. Ich bin mir nicht sicher, wie ich das nach all der Zeit verpasst habe.
ShadowChaser
Es ist fehlerhaft! Ich erinnere mich nicht genau, aber (soweit ich mich erinnere) führt es unerwünschte Schritte aus und kann zu hässlichen Nebenwirkungen führen (z. B. mit Datenleser). Die beste Lösung ist hier (Jeppe Stig Nielsen): stackoverflow.com/questions/13709626/…
SalientBrain
4

Interessanter Thread. Um eine Streaming-Version von Split / Partition zu erhalten, können Enumeratoren verwendet und mithilfe von Erweiterungsmethoden Sequenzen aus dem Enumerator ausgegeben werden. Das Konvertieren von imperativem Code in funktionalen Code unter Verwendung von Yield ist in der Tat eine sehr leistungsfähige Technik.

Zuerst eine Enumerator-Erweiterung, die eine Anzahl von Elementen in eine verzögerte Sequenz verwandelt:

public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
    while (count > 0)
    {
        yield return enumerator.Current;
        if (--count > 0 && !enumerator.MoveNext()) yield break;
    }
}

Und dann eine aufzählbare Erweiterung, die eine Sequenz partitioniert:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
    var enumerator = seq.GetEnumerator();

    while (enumerator.MoveNext())
    {
        yield return enumerator.TakeFromCurrent(partitionSize);
    }
}

Das Endergebnis ist eine hocheffiziente, Streaming- und Lazy-Implementierung, die auf sehr einfachem Code basiert.

Genießen!

Martin Fredriksson
quelle
Ich habe anfangs dasselbe programmiert, aber das Muster bricht ab, wenn Reset für eine der verschachtelten IEnumerable <T> -Instanzen aufgerufen wird.
ShadowChaser
1
Funktioniert dies immer noch, wenn Sie nur die Partition und nicht die innere Aufzählung auflisten? Da der innere Enumerator zurückgestellt wird, wird keiner der Codes für den inneren (aus dem Strom nehmen) ausgeführt, bis er aufgezählt wird. Daher wird movenext () nur von der äußeren Partitionsfunktion aufgerufen, oder? Wenn meine Annahmen zutreffen, kann dies möglicherweise zu n Partitionen mit n Elementen in der ursprünglichen Aufzählung führen, und die inneren Aufzählungen führen zu unerwarteten Ergebnissen
Brad,
@Brad es wird "scheitern", wie Sie erwarten, ähnlich wie einige der Probleme in diesem Thread stackoverflow.com/questions/419019/… (speziell stackoverflow.com/a/20953521/1037948 )
drzaus
4

Ich benutze das:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize)
{
    return instance
        .Select((value, index) => new { Index = index, Value = value })
        .GroupBy(i => i.Index / partitionSize)
        .Select(i => i.Select(i2 => i2.Value));
}
Elmer
quelle
Bitte erklären Sie warum. Ich habe diese Funktion ohne Probleme verwendet!
Elmer
Lesen Sie die Frage noch einmal und sehen Sie, ob Sie mit Ihrer Funktion n (fast) gleich lange Teile erhalten
Muhammad Hasan Khan
@ Elmer deine Antwort ist richtig, aber die Frage ist falsch dafür. Ihre Antwort gibt eine unbekannte Anzahl von Blöcken mit fester Größe für jeden Block an (genau wie Partition, der Name, den Sie dafür angegeben haben). OP möchte jedoch eine Split-Funktionalität, bei der eine feste Anzahl von Blöcken mit einer beliebigen Größe pro Block (hoffentlich gleich oder nahezu gleich groß) angegeben wird. Vielleicht besser geeignet hier stackoverflow.com/questions/3773403/…
nawfal
Ich denke, Sie können einfach i.Index / partitionSize in i.Index% partitionSize ändern und das angeforderte Ergebnis erhalten. Ich ziehe dies auch der akzeptierten Antwort vor, da es kompakter und lesbarer ist.
Jake Drew
2

Dies ist speichereffizient und verzögert die Ausführung so weit wie möglich (pro Stapel) und arbeitet in der linearen Zeit O (n).

    public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize)
    {
        List<T> batch = new List<T>(batchSize);
        foreach (var item in items)
        {
            batch.Add(item);

            if (batch.Count >= batchSize)
            {
                yield return batch;
                batch = new List<T>();
            }
        }

        if (batch.Count != 0)
        {
            //can't be batch size or would've yielded above
            batch.TrimExcess();
            yield return batch;
        }
    }
Brad
quelle
2

Es gibt viele gute Antworten auf diese Frage (und ihre Cousins). Ich brauchte dies selbst und hatte eine Lösung entwickelt, die effizient und fehlertolerant in einem Szenario ist, in dem die Quellensammlung als Liste behandelt werden kann. Es wird keine verzögerte Iteration verwendet, daher ist es möglicherweise nicht für Sammlungen unbekannter Größe geeignet, die möglicherweise Speicherdruck ausüben.

static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize)
{
    IList<T[]> result = null;
    if (source != null && batchsize > 0)
    {
        var list = source as List<T> ?? source.ToList();
        if (list.Count > 0)
        {
            result = new List<T[]>();
            for (var index = 0; index < list.Count; index += batchsize)
            {
                var rangesize = Math.Min(batchsize, list.Count - index);
                result.Add(list.GetRange(index, rangesize).ToArray());
            }
        }
    }
    return result ?? Enumerable.Empty<T[]>().ToList();
}

static public void TestGetChunks()
{
    var ids = Enumerable.Range(1, 163).Select(i => i.ToString());
    foreach (var chunk in ids.GetChunks(20))
    {
        Console.WriteLine("[{0}]", String.Join(",", chunk));
    }
}

Ich habe einige Antworten in dieser Familie von Fragen gesehen, die GetRange und Math.Min verwenden. Insgesamt glaube ich jedoch, dass dies eine vollständigere Lösung in Bezug auf Fehlerprüfung und Effizienz ist.

rhaben
quelle
1
   protected List<List<int>> MySplit(int MaxNumber, int Divider)
        {
            List<List<int>> lst = new List<List<int>>();
            int ListCount = 0;
            int d = MaxNumber / Divider;
            lst.Add(new List<int>());
            for (int i = 1; i <= MaxNumber; i++)
            {
                lst[ListCount].Add(i);
                if (i != 0 && i % d == 0)
                {
                    ListCount++;
                    d += MaxNumber / Divider;
                    lst.Add(new List<int>());
                }
            }
            return lst;
        }
Amit Sengar
quelle
1

Tolle Antworten, für mein Szenario habe ich die akzeptierte Antwort getestet und es scheint, dass sie nicht in Ordnung bleibt. Es gibt auch eine großartige Antwort von Nawfal, die Ordnung hält. Aber in meinem Szenario wollte ich den Rest auf normalisierte Weise aufteilen. Alle Antworten, die ich sah, verteilten den Rest oder am Anfang oder am Ende.

Meine Antwort geht auch davon aus, dass sich der Rest normalisiert.

 static class Program
{          
    static void Main(string[] args)
    {
        var input = new List<String>();
        for (int k = 0; k < 18; ++k)
        {
            input.Add(k.ToString());
        }
        var result = splitListIntoSmallerLists(input, 15);            
        int i = 0;
        foreach(var resul in result){
            Console.WriteLine("------Segment:" + i.ToString() + "--------");
            foreach(var res in resul){
                Console.WriteLine(res);
            }
            i++;
        }
        Console.ReadLine();
    }

    private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists)
    {
        if (i_numberOfSmallerLists <= 0)
            throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists");

        int normalizedSpreadRemainderCounter = 0;
        int normalizedSpreadNumber = 0;
        //e.g 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2          
        int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists;                        
        int remainder = i_bigList.Count % i_numberOfSmallerLists;
        int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder;
        //In case remainder > 0 we want to spread the remainder equally between the others         
        if (remainder > 0)
        {
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder);    
            }
            else
            {
                normalizedSpreadNumber = 1;
            }   
        }
        List<List<T>> retVal = new List<List<T>>(outputSize);
        int inputIndex = 0;            
        for (int i = 0; i < outputSize; ++i)
        {
            retVal.Add(new List<T>());
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList));
                inputIndex += minimumNumberOfPartsInEachSmallerList;
            }
            //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber.
            if (remainder > 0)
            {
                if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1)
                {
                    retVal[i].Add(i_bigList[inputIndex]);
                    remainder--;
                    inputIndex++;
                    normalizedSpreadRemainderCounter=0;
                }
                else
                {
                    normalizedSpreadRemainderCounter++;
                }
            }
        }
        return retVal;
    }      

}
Robocide
quelle
0

Wenn die Reihenfolge in diesen Teilen nicht sehr wichtig ist, können Sie Folgendes versuchen:

int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = 3;

var result =
   array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value);

// or
var result2 =
   from i in array.Select((value, index) => new { Value = value, Index = index })
   group i.Value by i.Index % n into g
   select g;

Diese können jedoch aus irgendeinem Grund nicht in IEnumerable <IEnumerable <int>> umgewandelt werden ...

okutane
quelle
Es kann getan werden. Anstelle von direktem Casting machen Sie die Funktion einfach generisch und rufen Sie sie dann für Ihr int-Array auf
nawfal
0

Das ist mein Code, nett und kurz.

 <Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T))
     Dim result As New List(Of List(Of T))
     For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1
         result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size)))))
     Next
     Return result
 End Function
Jonathan Allen
quelle
0

Dies ist meine Art, Elemente aufzulisten und Zeilen für Spalten aufzuteilen

  int repat_count=4;

  arrItems.ForEach((x, i) => {
    if (i % repat_count == 0) 
        row = tbo.NewElement(el_tr, cls_min_height);
    var td = row.NewElement(el_td);
    td.innerHTML = x.Name;
  });
IlPADlI
quelle
0

Ich habe nach einem Split wie dem mit String gesucht, daher wird die gesamte Liste nach einer Regel aufgeteilt, nicht nur nach dem ersten Teil, dies ist meine Lösung

List<int> sequence = new List<int>();
for (int i = 0; i < 2000; i++)
{
     sequence.Add(i);
}
int splitIndex = 900;
List<List<int>> splitted = new List<List<int>>();
while (sequence.Count != 0)
{
    splitted.Add(sequence.Take(splitIndex).ToList() );
    sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count));
}
Adel
quelle
Versuchen Sie es beim nächsten Mal: ​​var nrs = Enumerable.Range (1.2000) .ToList ();
MBoros
0

Hier ist eine kleine Änderung für die Anzahl der Elemente anstelle der Anzahl der Teile:

public static class MiscExctensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems)
    {
        return (
            list
            .Select((o, n) => new { o, n })
            .GroupBy(g => (int)(g.n / nbItems))
            .Select(g => g.Select(x => x.o))
        );
    }
}
JB
quelle
-1
int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 };

int itemIndex = 0;
int groupSize = 2;
int nextGroup = groupSize;

var seqItems = from aItem in items
               group aItem by 
                            (itemIndex++ < nextGroup) 
                            ? 
                            nextGroup / groupSize
                            :
                            (nextGroup += groupSize) / groupSize
                            into itemGroup
               select itemGroup.AsEnumerable();

quelle
-1

Ich bin gerade auf diesen Thread gestoßen, und die meisten Lösungen hier umfassen das Hinzufügen von Elementen zu Sammlungen, wodurch jede Seite effektiv materialisiert wird, bevor sie zurückgegeben wird. Dies ist aus zwei Gründen schlecht: Erstens, wenn Ihre Seiten groß sind, entsteht ein Speicheraufwand für das Füllen der Seite, zweitens gibt es Iteratoren, die frühere Datensätze ungültig machen, wenn Sie zum nächsten übergehen (z. B. wenn Sie einen DataReader in eine Enumerator-Methode einschließen). .

Diese Lösung verwendet zwei verschachtelte Enumerator-Methoden, um zu vermeiden, dass Elemente in temporären Sammlungen zwischengespeichert werden müssen. Da der äußere und der innere Iterator dieselbe Aufzählung durchlaufen, teilen sie sich notwendigerweise denselben Aufzähler. Daher ist es wichtig, den äußeren nicht vorzurücken, bis Sie mit der Verarbeitung der aktuellen Seite fertig sind. Wenn Sie sich jedoch dazu entschließen, die aktuelle Seite nicht vollständig zu durchlaufen, wird diese Lösung beim Wechseln zur nächsten Seite automatisch zur Seitengrenze weitergeleitet.

using System.Collections.Generic;

public static class EnumerableExtensions
{
    /// <summary>
    /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
    /// </summary>
    /// <typeparam name="T">The element type</typeparam>
    /// <param name="enumerable">The source enumerable</param>
    /// <param name="pageSize">The number of elements to return in each page</param>
    /// <returns></returns>
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
    {
        var enumerator = enumerable.GetEnumerator();

        while (enumerator.MoveNext())
        {
            var indexWithinPage = new IntByRef { Value = 0 };

            yield return SubPartition(enumerator, pageSize, indexWithinPage);

            // Continue iterating through any remaining items in the page, to align with the start of the next page
            for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
            }
        }
    }

    private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
    {
        for (; index.Value < pageSize; index.Value++)
        {
            yield return enumerator.Current;

            if (!enumerator.MoveNext())
            {
                yield break;
            }
        }
    }

    private class IntByRef
    {
        public int Value { get; set; }
    }
}
Jon G.
quelle
Das funktioniert überhaupt nicht! Das bestmögliche ist hier stackoverflow.com/questions/13709626/… ! Zeige Kommentare.
SalientBrain