wake-up-neo.com

Teilen Sie eine Liste in kleinere Listen der Größe N auf

Ich versuche, eine Liste in eine Reihe kleinerer Listen aufzuteilen.

Mein Problem: Meine Funktion zum Aufteilen von Listen teilt sie nicht in Listen mit der richtigen Größe auf. Es sollte sie in Listen der Größe 30 aufteilen, stattdessen in Listen der Größe 114?

Wie kann ich meine Funktion dazu bringen, eine Liste in X Anzahl der Listen der Größe 30 oder weniger aufzuteilen?

public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30) 
{       
    List<List<float[]>> list = new List<List<float[]>>();

    for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
        List <float[]> subLocat = new List <float[]>(locations); 

        if (subLocat.Count >= ((i*nSize)+nSize))
            subLocat.RemoveRange(i*nSize, nSize);
        else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));

        Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
        list.Add (subLocat);
    }

    return list;
}

Wenn ich die Funktion für eine Liste der Größe 144 verwende, lautet die Ausgabe:

Index: 4, Größe: 120
Index: 3, Größe: 114
Index: 2, Größe: 114
Index: 1, Größe: 114
Index: 0, Größe: 114

134
Jake M
public static List<List<float[]>> splitList(List<float[]> locations, int nSize=30)  
{        
    var list = new List<List<float[]>>(); 

    for (int i=0; i < locations.Count; i+= nSize) 
    { 
        list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
    } 

    return list; 
} 

Generische Version:

public static IEnumerable<List<T>> splitList<T>(List<T> locations, int nSize=30)  
{        
    for (int i=0; i < locations.Count; i+= nSize) 
    { 
        yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i)); 
    }  
} 
167
Serj-Tm

Ich würde vorschlagen, diese Erweiterungsmethode zu verwenden, um die Quellliste anhand der angegebenen Chunk-Größe in die Unterlisten aufzunehmen:

/// <summary>
/// Helper methods for the lists.
/// </summary>
public static class ListExtensions
{
    public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize) 
    {
        return source
            .Select((x, i) => new { Index = i, Value = x })
            .GroupBy(x => x.Index / chunkSize)
            .Select(x => x.Select(v => v.Value).ToList())
            .ToList();
    }
}

Wenn Sie beispielsweise die Liste der 18 Elemente um 5 Elemente pro Chunk legen, erhalten Sie die Liste der 4 Unterlisten mit den folgenden Elementen: 5-5-5-3.

300
Dmitry Pavlov

wie wäre es mit:

while(locations.Any())
{    
    list.Add(locations.Take(nSize).ToList());
    locations= locations.Skip(nSize).ToList();
}
30
Rafal

Serj-Tm-Lösung ist in Ordnung, auch dies ist die generische Version als Erweiterungsmethode für Listen (in eine statische Klasse):

public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30)
{
    List<List<T>> list = new List<List<T>>();
    for (int i = 0; i < items.Count; i += sliceSize)
        list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i)));
    return list;
} 
10
equintas

Die akzeptierte Antwort (Serj-Tm) finde ich am robustesten, aber ich möchte eine generische Version vorschlagen.

   public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30)
    {
        var list = new List<List<T>>();

        for (int i = 0; i < locations.Count; i += nSize)
        {
            list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
        }

        return list;
    }
6
Linas

Ich habe eine generische Methode, die alle Arten von Float akzeptieren würde, und es wurde Unit-getestet. Ich hoffe, es hilft:

    /// <summary>
    /// Breaks the list into groups with each group containing no more than the specified group size
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="values">The values.</param>
    /// <param name="groupSize">Size of the group.</param>
    /// <returns></returns>
    public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null)
    {
        List<List<T>> result = new List<List<T>>();
        // Quick and special scenario
        if (values.Count() <= groupSize)
        {
            result.Add(values.ToList());
        }
        else
        {
            List<T> valueList = values.ToList();
            int startIndex = 0;
            int count = valueList.Count;
            int elementCount = 0;

            while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount)))
            {
                elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize;
                result.Add(valueList.GetRange(startIndex, elementCount));
                startIndex += elementCount;
            }
        }


        return result;
    }
5
Tianzhen Lin

Während viele der obigen Antworten die Aufgabe erfüllen, scheitern sie alle bei einer endlosen Sequenz (oder einer wirklich langen Sequenz). Das Folgende ist eine vollständige Online-Implementierung, die die bestmögliche Zeit- und Speicherkomplexität garantiert. Die Iteration wird nur einmal genau wiederholt und der Renditeertrag wird für eine langsame Bewertung verwendet. Der Verbraucher könnte die Liste bei jeder Iteration wegwerfen, wodurch der Speicherabdruck dem der Liste mit der Anzahl der batchSize-Elemente entspricht.

public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, int batchSize)
{
    using (var enumerator = enumerable.GetEnumerator())
    {
        List<T> list = null;
        while (enumerator.MoveNext())
        {
            if (list == null)
            {
                list = new List<T> {enumerator.Current};
            }
            else if (list.Count < batchSize)
            {
                list.Add(enumerator.Current);
            }
            else
            {
                yield return list;
                list = new List<T> {enumerator.Current};
            }
        }

        if (list?.Count > 0)
        {
            yield return list;
        }
    }
}

BEARBEITEN: Wenn Sie gerade feststellen, dass das OP fragt, ob Sie einen List<T> in einen kleineren List<T> aufteilen, sind meine Kommentare zu unendlichen Aufzählungszeichen nicht auf das OP anwendbar, können aber anderen helfen, die hier landen. Diese Kommentare waren eine Antwort auf andere veröffentlichte Lösungen, die IEnumerable<T> als Eingabe für ihre Funktion verwenden, die Quelle jedoch mehrfach aufzählen.

3
mhand

Zusatz nach sehr nützlichem Kommentar von mhand am Ende

Ursprüngliche Antwort

Obwohl die meisten Lösungen möglicherweise funktionieren, sind sie meiner Meinung nach nicht sehr effizient. Angenommen, Sie möchten nur die ersten Elemente der ersten Brocken. Dann möchten Sie nicht alle Elemente (zillion) in Ihrer Sequenz durchlaufen.

Das Folgende wird höchstens zweimal aufgezählt: einmal für das Take und einmal für das Überspringen. Es werden nicht mehr Elemente aufgelistet, als Sie verwenden werden:

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>
    (this IEnumerable<TSource> source, int chunkSize)
{
    while (source.Any())                     // while there are elements left
    {   // still something to chunk:
        yield return source.Take(chunkSize); // return a chunk of chunkSize
        source = source.Skip(chunkSize);     // skip the returned chunk
    }
}

Wie oft wird die Sequenz aufgezählt?

Angenommen, Sie teilen Ihre Quelle in Brocken von chunkSize auf. Sie listen nur die ersten N-Blöcke auf. In jedem aufgelisteten Block werden nur die ersten M-Elemente aufgelistet.

While(source.Any())
{
     ...
}

any erhält den Enumerator, do 1 MoveNext () und gibt den zurückgegebenen Wert nach Disposing the Enumerator zurück. Dies wird N-mal durchgeführt

yield return source.Take(chunkSize);

Entsprechend der Bezugsquelle wird dies ungefähr so ​​aussehen:

public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
    return TakeIterator<TSource>(source, count);
}

static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    foreach (TSource element in source)
    {
        yield return element;
        if (--count == 0) break;
    }
}

Das tut nicht viel, bis Sie über den abgerufenen Chunk aufgezählt haben. Wenn Sie mehrere Chunks abrufen, sich jedoch nicht für den ersten Chunk entscheiden, wird Foreach nicht ausgeführt, wie Ihr Debugger Ihnen zeigen wird.

Wenn Sie sich entscheiden, die ersten M Elemente des ersten Abschnitts zu nehmen, wird die Rendite genau M-mal ausgeführt. Das heisst:

  • hol den Enumerator 
  • rufen Sie MoveNext () und Current M times auf. 
  • Entsorgen Sie den Enumerator

Nachdem das erste Stück zurückgegeben wurde, überspringen wir dieses erste Stück:

source = source.Skip(chunkSize);

Nochmals: Wir schauen uns Referenzquelle an, um die skipiterator zu finden.

static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
    using (IEnumerator<TSource> e = source.GetEnumerator()) 
    {
        while (count > 0 && e.MoveNext()) count--;
        if (count <= 0) 
        {
            while (e.MoveNext()) yield return e.Current;
        }
    }
}

Wie Sie sehen, ruft SkipIterator einmal MoveNext() für jedes Element im Chunk auf. Es ruft nicht Current auf.

Pro Chunk sehen wir also Folgendes:

  • Any (): GetEnumerator; 1 MoveNext (); Enumerator entsorgen;
  • Nehmen(): 

    • nichts, wenn der Inhalt des Blocks nicht aufgezählt wird. 
    • Wenn der Inhalt aufgezählt wird: GetEnumerator (), ein MoveNext- und ein Current-Element pro Aufzählungselement, Dispose-Enumerator;

    • Skip (): für jeden aufgelisteten Abschnitt (NICHT den Inhalt des Blocks): GetEnumerator (), MoveNext () chunkSize-Zeiten, kein Strom! Enumerator entsorgen

Wenn Sie sehen, was mit dem Enumerator passiert, werden Sie feststellen, dass MoveNext () viele Aufrufe enthält, und nur Aufrufe von Current für die TSource-Elemente, auf die Sie tatsächlich zugreifen möchten.

Wenn Sie N Chunks der Größe chunkSize einnehmen, werden Aufrufe von MoveNext () aufgerufen.

  • N-mal für Any ()
  • noch keine Zeit für Take, solange Sie die Chunks nicht auflisten
  • N mal chunkSize für Skip ()

Wenn Sie sich dafür entscheiden, nur die ersten M-Elemente jedes abgerufenen Chunks aufzulisten, müssen Sie MoveNext M-mal pro aufgelisteten Chunk aufrufen.

Die Summe

MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)

Wenn Sie also alle Elemente aller Chunks auflisten möchten:

MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once

Ob MoveNext viel Arbeit ist oder nicht, hängt vom Typ der Quellsequenz ab. Bei Listen und Arrays handelt es sich um ein einfaches Indexinkrement mit möglicherweise einer Überprüfung außerhalb des Bereichs.

Wenn Ihr IEnumerable das Ergebnis einer Datenbankabfrage ist, stellen Sie sicher, dass die Daten tatsächlich auf Ihrem Computer gespeichert sind. Andernfalls werden die Daten mehrmals abgerufen. DbContext und Dapper übertragen die Daten ordnungsgemäß in den lokalen Prozess, bevor auf sie zugegriffen werden kann. Wenn Sie dieselbe Sequenz mehrmals auflisten, wird sie nicht mehrmals abgerufen. Dapper gibt ein Objekt zurück, das eine Liste ist. DbContext erinnert sich daran, dass die Daten bereits abgerufen wurden.

Es hängt von Ihrem Repository ab, ob es ratsam ist, AsEnumerable () oder ToLists () aufzurufen, bevor Sie die Elemente in Chunks teilen

2
public static IEnumerable<IEnumerable<T>> SplitIntoSets<T>
    (this IEnumerable<T> source, int itemsPerSet) 
{
    var sourceList = source as List<T> ?? source.ToList();
    for (var index = 0; index < sourceList.Count; index += itemsPerSet)
    {
        yield return sourceList.Skip(index).Take(itemsPerSet);
    }
}
2
Scott Hannen

Bibliothek MoreLinq hat eine Methode namens Batch

List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements
int counter = 1;
foreach(var batch in ids.Batch(2))
{
    foreach(var eachId in batch)
    {
        Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId);
    }
    counter++;
}

Ergebnis ist

Batch: 1, Id: 1
Batch: 1, Id: 2
Batch: 2, Id: 3
Batch: 2, Id: 4
Batch: 3, Id: 5
Batch: 3, Id: 6
Batch: 4, Id: 7
Batch: 4, Id: 8
Batch: 5, Id: 9
Batch: 5, Id: 0

ids werden in 5 Chunks mit 2 Elementen aufgeteilt.

2
Sidron

Wie wäre es mit diesem? Die Idee war, nur eine Schleife zu verwenden. Und wer weiß, vielleicht verwenden Sie nur IList-Implementierungen für Ihren Code und möchten diese nicht in List umwandeln.

private IEnumerable<IList<T>> SplitList<T>(IList<T> list, int totalChunks)
{
    IList<T> auxList = new List<T>();
    int totalItems = list.Count();

    if (totalChunks <= 0)
    {
        yield return auxList;
    }
    else 
    {
        for (int i = 0; i < totalItems; i++)
        {               
            auxList.Add(list[i]);           

            if ((i + 1) % totalChunks == 0)
            {
                yield return auxList;
                auxList = new List<T>();                
            }

            else if (i == totalItems - 1)
            {
                yield return auxList;
            }
        }
    }   
}
1
Diego Romar
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
{
    return items.Select((item, index) => new { item, index })
                .GroupBy(x => x.index / maxItems)
                .Select(g => g.Select(x => x.item));
}
1
Codester
public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize)
    {           
        var result = new List<List<T>>();
        for (int i = 0; i < source.Count; i += chunkSize)
        {
            var rows = new List<T>();
            for (int j = i; j < i + chunkSize; j++)
            {
                if (j >= source.Count) break;
                rows.Add(source[j]);
            }
            result.Add(rows);
        }
        return result;
    }
0
Baskovli3

Einer noch

public static IList<IList<T>> SplitList<T>(this IList<T> list, int chunkSize)
{
    var chunks = new List<IList<T>>();
    List<T> chunk = null;
    for (var i = 0; i < list.Count; i++)
    {
        if (i % chunkSize == 0)
        {
            chunk = new List<T>(chunkSize);
            chunks.Add(chunk);
        }
        chunk.Add(list[i]);
    }
    return chunks;
}
0