Membagi daftar menjadi daftar ukuran N yang lebih kecil

210

Saya mencoba untuk membagi daftar menjadi serangkaian daftar yang lebih kecil.

Masalah Saya: Fungsi saya untuk membagi daftar tidak membaginya menjadi daftar dengan ukuran yang benar. Seharusnya membaginya menjadi daftar ukuran 30 tetapi sebaliknya membaginya menjadi daftar ukuran 114?

Bagaimana saya bisa membuat fungsi saya membagi daftar menjadi X jumlah Daftar ukuran 30 atau kurang ?

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

Jika saya menggunakan fungsi pada daftar ukuran 144 maka hasilnya adalah:

Indeks: 4, Ukuran: 120
Indeks: 3, Ukuran: 114
Indeks: 2, Ukuran: 114
Indeks: 1, Ukuran: 114
Indeks: 0, Ukuran: 114

sazr
sumber
1
Jika solusi LINQ dapat diterima, pertanyaan ini mungkin bisa membantu .
Secara khusus jawaban Sam Saffron pada pertanyaan sebelumnya. Dan kecuali ini untuk tugas sekolah, saya hanya akan menggunakan kodenya dan berhenti.
jcolebrand

Jawaban:

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

Versi generik:

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)); 
    }  
} 
Serj-Tm
sumber
Jadi jika saya memiliki Daftar panjang zillion, dan saya ingin membagi menjadi daftar yang lebih kecil Panjang 30, dan dari setiap daftar yang lebih kecil saya hanya ingin Ambil (1), maka saya masih membuat daftar 30 item yang saya buang 29 item. Ini bisa dilakukan dengan lebih cerdas!
Harald Coppoolse
Apakah ini berhasil? Bukankah itu gagal pada pemisahan pertama karena Anda mendapatkan kisaran nSize ke nSize? Sebagai contoh jika nSize adalah 3 dan array saya adalah ukuran 5 maka rentang indeks pertama yang dikembalikan adalahGetRange(3, 3)
Matthew Pigram
2
@ MatthewPigram diuji dan berfungsi. Math.Min mengambil nilai min jadi jika chunk terakhir kurang dari nSize (2 <3), itu membuat daftar dengan item yang tersisa.
Phate01
1
@HaraldCoppoolse OP tidak meminta untuk memilih, hanya untuk membagi daftar
Phate01
@MatthewPigram Iterasi pertama - GetRange (0,3), iterasi kedua - GetRange (3,2)
Serj-Tm
381

Saya akan menyarankan untuk menggunakan metode ekstensi ini untuk memotong daftar sumber ke sub-daftar dengan ukuran potongan yang ditentukan:

/// <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();
    }
}

Misalnya, jika Anda memotong daftar 18 item dengan 5 item per potong, itu memberi Anda daftar 4 sub-daftar dengan item berikut di dalam: 5-5-5-3.

Dmitry Pavlov
sumber
25
Sebelum Anda menggunakan ini dalam produksi, pastikan Anda memahami apa implikasi run-time untuk memori dan kinerja. Hanya karena LINQ bisa ringkas, bukan berarti itu ide yang bagus.
Nick
4
Jelas, @Nick saya sarankan secara umum untuk berpikir sebelum melakukan sesuatu. Chunking dengan LINQ seharusnya tidak menjadi operasi yang sering diulang ribuan kali. Biasanya Anda perlu memotong daftar untuk memproses item batch demi batch dan / atau secara paralel.
Dmitry Pavlov
6
Saya tidak berpikir memori dan kinerja harus menjadi masalah besar di sini. Saya kebetulan memiliki persyaratan untuk memecah daftar dengan lebih dari 200.000 catatan menjadi daftar yang lebih kecil dengan masing-masing sekitar 3.000, yang membawa saya ke utas ini, dan saya menguji kedua metode dan menemukan waktu berjalan hampir sama. Setelah itu saya menguji pemisahan daftar itu menjadi daftar dengan masing-masing 3 catatan dan tetap kinerjanya OK. Saya pikir solusi Serj-Tm lebih mudah dan memiliki rawatan yang lebih baik.
Silent Sojourner
2
Perhatikan bahwa mungkin yang terbaik adalah meninggalkan ToList()s, dan biarkan evaluasi malas melakukan keajaiban itu.
Yair Halberstadt
3
@DmitryPavlov Selama semua ini, aku tidak pernah tahu tentang mampu memproyeksikan indeks seperti itu dalam sebuah pernyataan pilih! Saya pikir itu adalah fitur baru sampai saya perhatikan Anda memposting ini pada tahun 2014, yang benar-benar mengejutkan saya! Terima kasih telah berbagi ini. Juga, tidak akan lebih baik untuk memiliki metode ekstensi ini tersedia untuk IEnumerable dan juga mengembalikan IEnumerable?
Aydin
37

bagaimana tentang:

while(locations.Any())
{    
    list.Add(locations.Take(nSize).ToList());
    locations= locations.Skip(nSize).ToList();
}
Rafal
sumber
Apakah ini akan menghabiskan banyak memori? Setiap kali location.Skip.ToList terjadi, saya bertanya-tanya apakah lebih banyak memori dialokasikan dan item yang tidak terkirim direferensikan oleh daftar baru.
Zasz
2
ya daftar baru dibuat di setiap loop. Ya itu menghabiskan memori. Tetapi jika Anda mengalami masalah memori, ini bukan tempat untuk mengoptimalkan karena instance dari daftar itu siap dikumpulkan pada loop berikutnya. Anda dapat memperdagangkan kinerja untuk memori dengan melompati ToListtetapi saya tidak akan repot-repot mencoba mengoptimalkannya - itu sangat sepele dan tidak mungkin merupakan hambatan. Keuntungan utama dari implementasi ini adalah hal-hal sepele yang mudah dipahami. Jika mau, Anda dapat menggunakan jawaban yang diterima itu tidak membuat daftar itu tetapi sedikit lebih rumit.
Rafal
2
.Skip(n)iterates atas nelemen setiap kali dipanggil, sementara ini mungkin ok, penting untuk mempertimbangkan kode kinerja-kritis. stackoverflow.com/questions/20002975/…
Chakrava
@ Chakrava yakin, solusi saya tidak akan digunakan dalam kode kinerja kritis, namun dalam pengalaman saya, Anda pertama kali menulis kode kerja dan kemudian menentukan apa yang kinerja kritis dan jarang di mana LINQ saya untuk operasi objek dilakukan pada mengatakan 50 objek. Ini harus dievaluasi kasus per kasus.
Rafal
@ Rafal Saya setuju, saya telah menemukan banyak .Skip()di basis kode perusahaan saya, dan meskipun mereka mungkin tidak "optimal" mereka bekerja dengan baik. Hal-hal seperti operasi DB membutuhkan waktu lebih lama. Tapi saya pikir ini hal yang penting untuk dicatat bahwa .Skip()"menyentuh" ​​setiap elemen <n di jalan bukannya langsung ke elemen-n secara langsung (seperti yang Anda harapkan). Jika iterator Anda memiliki efek samping dari menyentuh elemen .Skip()dapat menjadi penyebab bug yang sulit ditemukan.
Chakrava
11

Solusi Serj-Tm baik-baik saja, juga ini adalah versi generik sebagai metode ekstensi untuk daftar (memasukkannya ke dalam kelas statis):

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;
} 
equintas
sumber
10

Saya menemukan jawaban yang diterima (Serj-Tm) paling kuat, tetapi saya ingin menyarankan versi generik.

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;
}
Linas
sumber
8

Perpustakaan MoreLinq memiliki metode yang disebut 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++;
}

Hasilnya adalah

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 dibagi menjadi 5 bongkahan dengan 2 elemen.

Sidron
sumber
Ini perlu jawaban yang diterima. Atau setidaknya jauh lebih tinggi di halaman ini.
Zar Shardan
7

Saya memiliki metode generik yang akan mengambil semua jenis termasuk float, dan telah diuji unit, semoga membantu:

    /// <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;
    }
Tianzhen Lin
sumber
Terima kasih. Bertanya-tanya apakah Anda dapat memperbarui komentar dengan definisi parameter maxCount? Jaring pengaman?
Andrew Jens
2
hati-hati dengan beberapa enumerasi dari enumerable. values.Count()akan menyebabkan pencacahan penuh dan kemudian values.ToList()lainnya. Lebih aman untuk melakukannya values = values.ToList()sudah terwujud.
mhand
7

Sementara banyak jawaban di atas melakukan pekerjaan, semuanya gagal total pada urutan yang tidak pernah berakhir (atau urutan yang sangat panjang). Berikut ini adalah implementasi sepenuhnya on-line yang menjamin kompleksitas waktu dan memori terbaik. Kami hanya mengulangi sumber yang disebutkan sekali saja dan menggunakan pengembalian hasil untuk evaluasi malas. Konsumen dapat membuang daftar pada setiap iterasi yang membuat jejak memori sama dengan daftar dengan batchSizesejumlah elemen.

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

EDIT: Baru saja menyadari OP bertanya tentang memecah List<T>menjadi lebih kecil List<T>, jadi komentar saya tentang enumerables tak terbatas tidak berlaku untuk OP, tetapi dapat membantu orang lain yang berakhir di sini. Komentar-komentar ini sebagai tanggapan terhadap solusi diposting lainnya yang memang digunakan IEnumerable<T>sebagai input untuk fungsi mereka, namun menghitung sumber yang dapat dihitung berulang kali.

tangan
sumber
Saya pikir IEnumerable<IEnumerable<T>>versinya lebih baik karena tidak melibatkan banyak Listkonstruksi.
NetMage
@ NetMage - satu masalah dengan IEnumerable<IEnumerable<T>>adalah bahwa implementasi cenderung bergantung pada konsumen yang secara penuh menghitung setiap bagian yang dapat dihitung. Saya yakin solusi dapat diutarakan dengan cara untuk menghindari masalah itu, tapi saya pikir kode yang dihasilkan bisa menjadi sangat cepat. Juga, karena malas, kami hanya membuat satu daftar pada satu waktu dan alokasi memori terjadi tepat satu kali per daftar karena kami tahu ukuran di muka.
mhand
Anda benar - implementasi saya menggunakan enumerator jenis baru (Pencacah Posisi) yang melacak posisi Anda saat ini yang membungkus pencacah standar dan membiarkan Anda pindah ke posisi baru.
NetMage
6

Tambahan setelah komentar yang sangat berguna dari mhand di akhir

Jawaban asli

Meskipun sebagian besar solusi mungkin berhasil, saya pikir mereka tidak terlalu efisien. Misalkan jika Anda hanya menginginkan beberapa item pertama dari beberapa chunks pertama. Maka Anda tidak ingin mengulangi semua (miliaran) item dalam urutan Anda.

Berikut ini akan paling banyak menyebutkan dua kali: sekali untuk Ambil dan sekali untuk Lewati. Itu tidak akan menghitung lebih dari elemen daripada yang akan Anda gunakan:

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

Berapa kali ini akan menghitung urutan?

Misalkan Anda membagi sumber Anda menjadi beberapa bagian chunkSize. Anda hanya menghitung potongan N pertama. Dari setiap potongan yang disebutkan, Anda hanya akan menghitung elemen M pertama.

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

Any akan mendapatkan Enumerator, lakukan 1 MoveNext () dan kembalikan nilai yang dikembalikan setelah Membuang Enumerator. Ini akan dilakukan N kali

yield return source.Take(chunkSize);

Menurut sumber referensi ini akan melakukan sesuatu seperti:

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

Ini tidak banyak membantu sampai Anda mulai menghitung lebih dari Chunk yang diambil. Jika Anda mengambil beberapa bongkahan, tetapi memutuskan untuk tidak menghitung lebih dari bongkahan pertama, foreach tidak dieksekusi, karena debugger Anda akan menunjukkan kepada Anda.

Jika Anda memutuskan untuk mengambil elemen M pertama dari chunk pertama maka pengembalian hasil dieksekusi tepat M kali. Ini berarti:

  • dapatkan enumerator
  • panggil MoveNext () dan M kali ini.
  • Buang enumerator

Setelah potongan pertama telah dikembalikan, kami lewati Potongan pertama ini:

source = source.Skip(chunkSize);

Sekali lagi: kita akan melihat sumber referensi untuk menemukanskipiterator

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

Seperti yang Anda lihat, SkipIteratorpanggilan MoveNext()satu kali untuk setiap elemen di dalam Chunk. Itu tidak menelepon Current.

Jadi per Chunk kita melihat bahwa berikut ini dilakukan:

  • Any (): GetEnumerator; 1 MoveNext (); Buang Enumerator;
  • Mengambil():

    • tidak ada apa-apa jika isi chunk tidak disebutkan.
    • Jika konten disebutkan: GetEnumerator (), satu MoveNext dan satu Current per item yang disebutkan, Buang enumerator;

    • Lewati (): untuk setiap chunk yang disebutkan (BUKAN isi chunk): GetEnumerator (), MoveNext () chunkUkuran kali, tanpa arus! Buang enumerator

Jika Anda melihat apa yang terjadi dengan enumerator, Anda akan melihat bahwa ada banyak panggilan ke MoveNext (), dan hanya panggilan ke Currentuntuk item TSource yang Anda putuskan untuk akses.

Jika Anda mengambil N Potongan ukuran chunkSize, maka panggilan ke MoveNext ()

  • N kali untuk Apa saja ()
  • belum waktunya untuk Take, asalkan Anda tidak menyebutkan Chunks
  • N kali chunkSize untuk Lewati ()

Jika Anda memutuskan untuk menghitung hanya elemen M pertama dari setiap chunk yang diambil, maka Anda perlu memanggil MoveNext M kali per Chunk yang disebutkan.

Jumlah seluruhnya

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

Jadi, jika Anda memutuskan untuk menghitung semua elemen dari semua bongkahan:

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

Apakah MoveNext banyak pekerjaan atau tidak, tergantung pada jenis urutan sumber. Untuk daftar dan array, ini adalah kenaikan indeks sederhana, dengan mungkin pemeriksaan di luar rentang.

Tetapi jika IEnumerable Anda adalah hasil dari query database, pastikan bahwa data benar-benar terwujud di komputer Anda, jika tidak, data akan diambil beberapa kali. DbContext dan Dapper akan mentransfer data dengan benar ke proses lokal sebelum dapat diakses. Jika Anda menyebutkan urutan yang sama beberapa kali, itu tidak diambil beberapa kali. Dapper mengembalikan objek yang Daftar, DbContext mengingat bahwa data sudah diambil.

Tergantung pada Repositori Anda apakah bijaksana untuk memanggil AsEnumerable () atau ToLists () sebelum Anda mulai membagi item dalam Potongan

Harald Coppoolse
sumber
tidakkah ini menghitung dua kali per batch? jadi kita benar-benar menghitung waktu sumber 2*chunkSize? Ini mematikan tergantung pada sumber yang dapat dihitung (mungkin DB didukung, atau sumber non-memoized lainnya). Bayangkan enumerable ini sebagai input Enumerable.Range(0, 10000).Select(i => DateTime.UtcNow)- Anda akan mendapatkan waktu yang berbeda setiap kali Anda menghitung enumerable karena itu tidak di-memo
mhand
Pertimbangkan: Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). Dengan memohon AnyAnda akan menghitung ulang waktu saat ini setiap kali. Tidak terlalu buruk untuk DateTime.UtcNow, tetapi pertimbangkan enumerable yang didukung oleh koneksi database / sql cursor atau yang serupa. Saya telah melihat kasus-kasus di mana ribuan panggilan DB dikeluarkan karena pengembang tidak memahami kemungkinan dampak 'enumerasi ganda yang dapat dihitung' - ReSharper juga memberikan petunjuk untuk ini juga
saja
4
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);
    }
}
Scott Hannen
sumber
3
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));
}
Codester
sumber
2

Bagaimana dengan yang ini? Idenya adalah menggunakan hanya satu loop. Dan, siapa tahu, mungkin Anda hanya menggunakan implementasi IList melalui kode Anda dan Anda tidak ingin memasukkan ke Daftar.

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;
            }
        }
    }   
}
Diego Romar
sumber
1

Satu lagi

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;
}
Gabriel Medeiros
sumber
1
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;
    }
Baskovli3
sumber
0
List<int> list =new List<int>(){1,2,3,4,5,6,7,8,9,10,12};
Dictionary<int,List<int>> dic = new Dictionary <int,List<int>> ();
int batchcount = list.Count/2; //To List into two 2 parts if you want three give three
List<int> lst = new List<int>();
for (int i=0;i<list.Count; i++)
{
lstdocs.Add(list[i]);
if (i % batchCount == 0 && i!=0)
{
Dic.Add(threadId, lstdocs);
lst = new List<int>();**strong text**
threadId++;
}
}
Dic.Add(threadId, lstdocs);
ANNAPUREDDY PRAVEEN KUMAR REDD
sumber
2
lebih baik menjelaskan jawaban Anda daripada hanya memberikan cuplikan kode
Kevin
0

Saya telah menemui kebutuhan yang sama ini, dan saya menggunakan kombinasi metode Lewati () dan Ambil () . Saya mengalikan jumlah yang saya ambil dengan jumlah iterasi sejauh ini, dan itu memberi saya jumlah item untuk dilewati, kemudian saya mengambil grup berikutnya.

        var categories = Properties.Settings.Default.MovementStatsCategories;
        var items = summariesWithinYear
            .Select(s =>  s.sku).Distinct().ToList();

        //need to run by chunks of 10,000
        var count = items.Count;
        var counter = 0;
        var numToTake = 10000;

        while (count > 0)
        {
            var itemsChunk = items.Skip(numToTake * counter).Take(numToTake).ToList();
            counter += 1;

            MovementHistoryUtilities.RecordMovementHistoryStatsBulk(itemsChunk, categories, nLogger);

            count -= numToTake;
        }
BeccaGirl
sumber
0

Berdasarkan Dimitry Pavlov jawab saya akan menghapus .ToList(). Dan juga menghindari kelas anonim. Sebaliknya saya suka menggunakan struct yang tidak memerlukan alokasi memori tumpukan. (A ValueTuplejuga akan melakukan pekerjaan.)

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
    if (source is null)
    {
        throw new ArgumentNullException(nameof(source));
    }
    if (chunkSize <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(chunkSize), chunkSize, "The argument must be greater than zero.");
    }

    return source
        .Select((x, i) => new ChunkedValue<TSource>(x, i / chunkSize))
        .GroupBy(cv => cv.ChunkIndex)
        .Select(g => g.Select(cv => cv.Value));
} 

[StructLayout(LayoutKind.Auto)]
[DebuggerDisplay("{" + nameof(ChunkedValue<T>.ChunkIndex) + "}: {" + nameof(ChunkedValue<T>.Value) + "}")]
private struct ChunkedValue<T>
{
    public ChunkedValue(T value, int chunkIndex)
    {
        this.ChunkIndex = chunkIndex;
        this.Value = value;
    }

    public int ChunkIndex { get; }

    public T Value { get; }
}

Ini dapat digunakan seperti berikut ini yang hanya mengulang koleksi sekali dan juga tidak mengalokasikan memori signifikan.

int chunkSize = 30;
foreach (var chunk in collection.ChunkBy(chunkSize))
{
    foreach (var item in chunk)
    {
        // your code for item here.
    }
}

Jika daftar konkret benar-benar diperlukan maka saya akan melakukannya seperti ini:

int chunkSize = 30;
var chunkList = new List<List<T>>();
foreach (var chunk in collection.ChunkBy(chunkSize))
{
    // create a list with the correct capacity to be able to contain one chunk
    // to avoid the resizing (additional memory allocation and memory copy) within the List<T>.
    var list = new List<T>(chunkSize);
    list.AddRange(chunk);
    chunkList.Add(list);
}
TiltonJH
sumber