Pisahkan koleksi menjadi bagian `n` dengan LINQ?

122

Adakah cara yang bagus untuk membagi koleksi n beberapa bagian dengan LINQ? Belum tentu merata tentunya.

Yaitu, saya ingin membagi koleksi menjadi sub-koleksi, yang masing-masing berisi subset elemen, di mana koleksi terakhir bisa compang-camping.

Simon_Weaver
sumber
1
Ditandai ulang: Pertanyaannya tidak ada hubungannya dengan asp.net. Harap beri tag pertanyaan Anda dengan tepat.
Bagaimana tepatnya Anda ingin mereka terbelah, jika tidak genap (membiarkan akhir, tentu saja)?
Marc Gravell
1
siapa yang terkait dengan pertanyaan ini? john apakah itu kamu? :-) tiba-tiba semua jawaban ini :-)
Simon_Weaver
Pertanyaan terkait Pisahkan Daftar menjadi Sublist dengan LINQ
Gennady Vanin Геннадий Ванин
@Simon_Weaver Saya mencoba menjelaskan apa yang Anda tanyakan berdasarkan jawaban yang diterima. Faktanya, ada banyak cara untuk 'memisahkan' sebuah daftar, termasuk menguraikan setiap elemen dari daftar menjadi elemen-elemennya, dan memasukkannya ke dalam apa yang disebut daftar 'paralel'.
jpaugh

Jawaban:

127

Linq murni dan solusi paling sederhana adalah seperti yang ditunjukkan di bawah ini.

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
sumber
3
Anda dapat melakukan: pilih part.AsEnumerable () daripada pilih (IEnumerable <T>) part. Rasanya lebih elegan.
tuinstoel
2
Melakukan semua operasi modulus itu bisa menjadi sedikit mahal pada daftar yang panjang.
Jonathan Allen
8
Akan lebih baik menggunakan Select overload yang menyertakan indeks.
Marc Gravell
1
Saya telah menambahkan respons yang menggunakan sintaks overload pilih dan rangkaian metode
reustmd
1
.AsEnumerable()tidak diperlukan, IGrouping <T> sudah menjadi IEnumerable <T>.
Alex
58

EDIT: Oke, sepertinya saya salah membaca pertanyaan. Saya membacanya sebagai "potongan dengan panjang n" daripada "potongan n". Doh! Mempertimbangkan untuk menghapus jawaban ...

(Jawaban asli)

Saya tidak percaya ada cara untuk mempartisi, meskipun saya bermaksud untuk menulis satu cara untuk menambahkan LINQ ke Objek. Marc Gravell memiliki implementasi di sini meskipun saya mungkin akan memodifikasinya untuk mengembalikan tampilan hanya-baca:

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
sumber
Sial - kalahkan aku ;-p
Marc Gravell
3
Anda benar - benar tidak menyukai "array [count ++]", eh ;-p
Marc Gravell
18
Terima kasih untuk tidak menghapus meskipun itu bukan jawaban untuk OP, saya ingin hal yang sama persis - potongan panjangnya n :).
Gishu
2
@Dejan: Tidak, tidak. Perhatikan penggunaan yield return. Ini membutuhkan satu batch untuk berada di memori pada satu waktu, tapi itu saja.
Jon Skeet
1
@Dejan: Benar - Saya tidak ingin menebak tentang bagaimana itu berinteraksi dengan partisi LINQ Paralel, jujur ​​:)
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
sumber
28
Saya memiliki ketidaksukaan yang tidak rasional terhadap SQL-style Linq, jadi ini adalah jawaban favorit saya.
piedar
1
@ manu08, saya telah mencoba kode ur, saya punya daftar var dept = {1,2,3,4,5}. Setelah dipecah hasilnya seperti dept1 = {1,3,5}dan dept2 = { 2,4 }dimana parts = 2. Tetapi hasil yang saya butuhkan adalah dept1 = {1,2,3}dandept2 = {4,5}
Karthik Arthik
3
Saya memiliki masalah yang sama dengan modulo, jadi saya menghitung panjang kolom dengan int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);kemudian melakukan pembagian .GroupBy(x => x.index / columnLength). Satu downside adalah Count () menyebutkan daftar.
goodeye
24

Oke, saya akan melempar topi saya ke atas ring. Keuntungan dari algoritme saya:

  1. Tidak ada operator perkalian, pembagian, atau modulus yang mahal
  2. Semua operasi adalah O (1) (lihat catatan di bawah)
  3. Berfungsi untuk IEnumerable <> source (tidak diperlukan properti Hitungan)
  4. Sederhana

Kode:

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

Seperti yang ditunjukkan pada komentar di bawah, pendekatan ini tidak benar-benar menjawab pertanyaan awal yang meminta sejumlah bagian tetap dengan panjang yang kira-kira sama. Meskipun demikian, Anda masih dapat menggunakan pendekatan saya untuk menyelesaikan pertanyaan asli dengan menyebutnya seperti ini:

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

Jika digunakan dengan cara ini, pendekatannya bukan lagi O (1) karena operasi Count () adalah O (N).

Mike
sumber
Brilliant - solusi terbaik di sini! Beberapa optimasi: * Hapus daftar tertaut alih-alih membuat yang baru untuk setiap bagian. Referensi ke daftar tertaut tidak pernah dikembalikan ke pemanggil, jadi ini sepenuhnya aman. * Jangan membuat daftar tertaut sampai Anda mencapai item pertama - dengan begitu tidak ada alokasi jika sumbernya kosong
ShadowChaser
3
@ShadowChaser Menurut MSDN, membersihkan LinkedList adalah O (N) kompleksitas sehingga akan merusak tujuan saya O (1). Tentu saja, Anda dapat berargumen bahwa foreach adalah O (N) untuk memulai dengan ... :)
Mike
4
jawaban Anda benar, tetapi pertanyaannya salah. Jawaban Anda memberikan jumlah potongan yang tidak diketahui dengan ukuran tetap untuk setiap potongan. Tetapi OP menginginkan fungsionalitas Split di mana ia memberikan jumlah potongan tetap dengan ukuran berapa pun per potongan (semoga berukuran sama atau mendekati ukuran yang sama). Mungkin lebih cocok di sini stackoverflow.com/questions/3773403/…
nawfal
1
@Mike sudahkah Anda membandingkannya? Saya harap Anda tahu O (1) tidak berarti lebih cepat, itu hanya berarti waktu yang diperlukan untuk mempartisi tidak berskala. Saya hanya ingin tahu apa alasan Anda untuk secara membabi buta tetap berpegang pada O (1) ketika itu bisa lebih lambat daripada O (n) lainnya untuk semua skenario kehidupan nyata. Saya bahkan mengujinya untuk daftar kekuatan 10 ^ 8 yang gila dan milik saya tampaknya lebih cepat lagi. Saya harap Anda tahu bahkan tidak ada jenis koleksi standar yang dapat menampung 10 ^ 12 item ..
nawfal
1
@nawfal - Terima kasih atas analisis rinci Anda, ini membantu saya tetap waspada. Daftar tertaut pada umumnya dikenal dengan sisipan akhir yang efisien, itulah sebabnya saya memilihnya di sini. Namun saya hanya membandingkannya dan memang List <> jauh lebih cepat. Saya menduga ini adalah semacam detail implementasi .NET, mungkin layak mendapatkan pertanyaan StackOverflow terpisah. Saya telah mengubah jawaban saya untuk menggunakan Daftar <> sesuai saran Anda. Prealokasi kapasitas daftar menjamin bahwa penyisipan akhir masih O (1) dan memenuhi tujuan desain asli saya. Saya juga beralih ke built-in .AsReadOnly () di .NET 4.5.
Mike
16

Ini sama dengan jawaban yang diterima, tetapi representasi yang jauh lebih sederhana:

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

Metode di atas membagi IEnumerable<T>menjadi sejumlah N potongan dengan ukuran yang sama atau mendekati ukuran yang sama.

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

Metode di atas membagi IEnumerable<T>menjadi potongan-potongan dengan ukuran tetap yang diinginkan dengan jumlah total potongan menjadi tidak penting - yang bukan pertanyaannya.

Masalah dengan Splitmetode ini, selain lebih lambat, adalah ia mengacak keluaran dalam arti bahwa pengelompokan akan dilakukan berdasarkan kelipatan ke-i dari N untuk setiap posisi, atau dengan kata lain Anda tidak mendapatkan potongannya. dalam urutan aslinya.

Hampir setiap jawaban di sini tidak menjaga ketertiban, atau tentang mempartisi dan tidak memisahkan, atau jelas salah. Coba ini yang lebih cepat, menjaga ketertiban tetapi sedikit lebih bertele-tele:

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

Metode yang setara untuk Partitionoperasi di sini

nawfal
sumber
6

Saya telah menggunakan fungsi Partisi yang saya posting sebelumnya cukup sering. Satu-satunya hal buruk tentang itu adalah tidak sepenuhnya streaming. Ini bukan masalah jika Anda bekerja dengan sedikit elemen dalam urutan Anda. Saya membutuhkan solusi baru ketika saya mulai bekerja dengan 100.000+ elemen dalam urutan saya.

Solusi berikut jauh lebih kompleks (dan lebih banyak kode!), Tetapi sangat efisien.

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

    }
}

Nikmati!

Elmer
sumber
Versi ini melanggar kontrak IEnumerator. Tidak valid untuk membuang InvalidOperationException saat Reset dipanggil - Saya yakin banyak metode ekstensi LINQ bergantung pada perilaku ini.
ShadowChaser
1
@ShadowChaser Saya pikir Reset () harus melempar NotSupportedException dan semuanya akan baik-baik saja. Dari dokumentasi MSDN: "Metode Reset disediakan untuk interoperabilitas COM. Metode ini tidak perlu diterapkan; sebagai gantinya, pelaksana dapat dengan mudah menampilkan NotSupportedException."
toong
@ Toong Wow, kamu benar. Tidak yakin bagaimana saya melewatkannya setelah sekian lama.
ShadowChaser
Ini buggy! Saya tidak ingat persis, tetapi (sejauh yang saya ingat) itu melakukan langkah yang tidak diinginkan dan dapat menyebabkan efek samping yang buruk (dengan datareader untuk ex.). Solusi terbaik ada di sini (Jeppe Stig Nielsen): stackoverflow.com/questions/13709626/…
SalientBrain
4

Benang yang menarik. Untuk mendapatkan versi streaming Split / Partition, seseorang dapat menggunakan enumerator dan urutan hasil dari enumerator menggunakan metode ekstensi. Mengubah kode imperatif menjadi kode fungsional menggunakan hasil adalah teknik yang sangat ampuh.

Pertama, ekstensi enumerator yang mengubah hitungan elemen menjadi urutan malas:

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

Dan kemudian ekstensi yang dapat dihitung yang mempartisi urutan:

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

Hasil akhirnya adalah implementasi yang sangat efisien, streaming, dan malas yang mengandalkan kode yang sangat sederhana.

Nikmati!

Martin Fredriksson
sumber
Saya awalnya memprogram hal yang sama, tetapi polanya rusak ketika Reset dipanggil pada salah satu instance IEnumerable <T> bersarang.
ShadowChaser
1
Apakah ini masih berfungsi jika Anda hanya menghitung partisi dan bukan bagian dalam yang dapat dihitung? karena enumerator dalam ditangguhkan maka tidak ada kode untuk bagian dalam (ambil dari saat ini) yang akan dieksekusi hingga dicacah oleh karena itu movenext () hanya akan dipanggil oleh fungsi partisi luar, bukan? Jika asumsi saya benar maka ini berpotensi menghasilkan n partisi dengan n elemen dalam enumerabel asli dan enumerabel dalam akan menghasilkan hasil yang tidak terduga
Brad
@Brad itu akan "gagal" seperti yang Anda harapkan, mirip dengan beberapa masalah di utas ini stackoverflow.com/questions/419019/… (khususnya stackoverflow.com/a/20953521/1037948 )
drzaus
4

Saya menggunakan ini:

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
sumber
Tolong jelaskan mengapa. Saya telah menggunakan fungsi ini tanpa masalah!
Elmer
baca pertanyaannya lagi dan lihat apakah Anda mendapatkan n (hampir) bagian dengan panjang yang sama dengan fungsi Anda
Muhammad Hasan Khan
@Elmer jawaban Anda benar, tetapi pertanyaannya salah untuk itu. Jawaban Anda memberikan jumlah potongan yang tidak diketahui dengan ukuran tetap untuk setiap potongan (persis seperti Partisi, nama yang Anda berikan untuk itu). Tetapi OP menginginkan fungsionalitas Split di mana ia memberikan jumlah potongan tetap dengan ukuran berapa pun per potongan (semoga berukuran sama atau mendekati ukuran yang sama). Mungkin lebih cocok di sini stackoverflow.com/questions/3773403/…
nawfal
Saya pikir Anda bisa mengubah i.Index / partitionSize menjadi i.Index% partitionSize dan mendapatkan hasil yang diminta. Saya juga lebih suka ini daripada jawaban yang diterima karena lebih ringkas dan mudah dibaca.
Jake Drew
2

Ini adalah efisiensi memori dan menunda eksekusi sebanyak mungkin (per batch) dan beroperasi dalam waktu linier 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
sumber
2

Ada banyak jawaban bagus untuk pertanyaan ini (dan sepupunya). Saya membutuhkan ini sendiri dan telah membuat solusi yang dirancang agar efisien dan toleran terhadap kesalahan dalam skenario di mana kumpulan sumber dapat diperlakukan sebagai daftar. Itu tidak menggunakan iterasi malas sehingga mungkin tidak cocok untuk koleksi dengan ukuran yang tidak diketahui yang mungkin menerapkan tekanan memori.

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

Saya telah melihat beberapa jawaban di seluruh keluarga pertanyaan ini yang menggunakan GetRange dan Math.Min. Tapi saya yakin secara keseluruhan ini adalah solusi yang lebih lengkap dalam hal pengecekan kesalahan dan efisiensi.

rhaben.dll
sumber
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
sumber
1

Jawaban Hebat, untuk skenario saya, saya menguji jawaban yang diterima, dan tampaknya itu tidak menjaga ketertiban. Ada juga jawaban bagus dari Nawfal yang menjaga ketertiban. Tetapi dalam skenario saya, saya ingin membagi sisanya dengan cara yang dinormalisasi, semua jawaban yang saya lihat menyebarkan sisanya atau di awal atau di akhir.

Jawaban saya juga membuat sisanya menyebar dengan cara yang lebih normal.

 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
sumber
0

Jika urutan di bagian ini tidak terlalu penting, Anda dapat mencoba ini:

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;

Namun ini tidak dapat dilemparkan ke IEnumerable <IEnumerable <int>> karena alasan tertentu ...

okutane
sumber
Itu bisa dilakukan. Alih-alih casting langsung, buat saja fungsinya generik dan kemudian panggil untuk array int Anda
nawfal
0

Ini kode saya, bagus dan pendek.

 <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
sumber
0

Ini adalah cara saya, membuat daftar item dan memecahkan baris demi kolom

  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
sumber
0

Saya mencari perpecahan seperti yang ada string, jadi seluruh Daftar dipecah menurut beberapa aturan, tidak hanya bagian pertama, ini adalah solusi saya

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
sumber
Lain kali coba: var nrs = Enumerable.Range (1,2000) .ToList ();
MBoros
0

Berikut ini sedikit perubahan untuk jumlah item, bukan jumlah bagian:

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
sumber
-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();

sumber
-1

Baru saja menemukan utas ini, dan sebagian besar solusi di sini melibatkan penambahan item ke koleksi, yang secara efektif mewujudkan setiap halaman sebelum mengembalikannya. Ini buruk karena dua alasan - pertama jika halaman Anda besar, ada overhead memori untuk mengisi halaman, kedua ada iterator yang membuat catatan sebelumnya tidak valid saat Anda melanjutkan ke yang berikutnya (misalnya jika Anda menggabungkan DataReader dalam metode enumerator) .

Solusi ini menggunakan dua metode enumerator bersarang untuk menghindari kebutuhan untuk menyimpan item ke dalam cache sementara. Karena iterator luar dan dalam melintasi enumerator yang sama, mereka selalu berbagi enumerator yang sama, jadi penting untuk tidak memajukan yang luar sampai Anda selesai memproses halaman saat ini. Meskipun demikian, jika Anda memutuskan untuk tidak mengulang sepanjang halaman saat ini, saat Anda pindah ke halaman berikutnya, solusi ini akan beralih maju ke batas halaman secara otomatis.

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
sumber
Ini tidak bekerja sama sekali! Kemungkinan terbaik ada di sini stackoverflow.com/questions/13709626/… ! Lihat komentar.
SalientBrain