Bir Listeyi daha küçük N boyutlu listelere bölme


209

Bir listeyi daha küçük listelere ayırmaya çalışıyorum.

Sorunum: Listeleri bölme işlevim onları doğru boyuttaki listelere ayırmıyor. Onları 30 büyüklükteki listelere ayırmalı, bunun yerine 114 büyüklüğündeki listelere böler mi?

İşlevimin bir listeyi 30 veya daha küçük boyuttaki X Liste sayısına bölmesini nasıl sağlayabilirim ?

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

144 boyutunda bir listede işlevi kullanırsanız, çıktı:

Dizin: 4, Boyut: 120
Dizin: 3, Boyut: 114
Dizin: 2, Boyut: 114
Dizin: 1, Boyut: 114
Dizin: 0, Boyut: 114


1
Bir LINQ çözümü kabul edilebilirse, bu soru yardımcı olabilir .

Özellikle Sam Saffron'un bu önceki soruya vereceği cevap. Ve bu bir okul ödevi için değilse, sadece kodunu kullanır ve dururdum.
jcolebrand

Yanıtlar:


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

Genel sürüm:

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

Liste uzunluğu milyonum varsa ve daha küçük listelere Uzunluk 30'a bölünmek istiyorsam ve sadece daha küçük listelerden almak istiyorum (1), o zaman hala 29 öğeyi attığım 30 öğeden oluşan listeler oluşturuyorum. Bu daha akıllıca yapılabilir!
Harald Coppoolse

Bu gerçekten işe yarıyor mu? NSize aralığını nSize olarak aldığınız için ilk bölmede başarısız olmaz mı? Örneğin, nSize 3 ve dizim 5 boyutundaysa, döndürülen ilk dizin aralığıGetRange(3, 3)
Matthew Pigram

2
@ MathewPigram test edildi ve çalışıyor. Math.Min min değerini alır, bu nedenle son yığın nSize'den (2 <3) küçükse kalan öğeleri içeren bir liste oluşturur.
Phate01

1
@HaraldCoppoolse OP, yalnızca listeleri bölmek için seçmek istemedi
Phate01

@MatthewPigram İlk yineleme - GetRange (0,3), ikinci yineleme - GetRange (3,2)
Serj-Tm

381

Kaynak listesini alt listelere belirtilen yığın boyutuna göre yığınlamak için bu uzantı yöntemini kullanmanızı öneririm:

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

Örneğin, 18 öğenin listesini yığın başına 5 öğeyle yığınlarsanız, içinde aşağıdaki öğelerin bulunduğu 4 alt listenin listesini verir: 5-5-5-3.


25
Bunu üretimde kullanmadan önce, bellek ve performans için çalışma zamanı etkilerinin ne olduğunu anladığınızdan emin olun. LINQ'nun özlü olabilmesi iyi bir fikir olduğu anlamına gelmez.
Nick

4
Kesinlikle, @Nick Genel olarak bir şey yapmadan önce düşünmenizi öneririm. LINQ ile tıkanıklık, binlerce kez tekrarlanan bir operasyon olmamalıdır. Genellikle öğeleri toplu olarak ve / veya paralel olarak işlemek için listeleri yığınlamanız gerekir.
Dmitry Pavlov

6
Bellek ve performansın burada büyük bir sorun olması gerektiğini düşünmüyorum. 200.000'den fazla kayıt içeren bir listeyi her biri yaklaşık 3000 olan daha küçük listelere bölme gereksinimim vardı, bu da beni bu konuya getirdi ve her iki yöntemi de test ettim ve çalışma süresinin neredeyse aynı olduğunu buldum. Bundan sonra, bu listeyi her biri 3 kayıt içeren listelere bölmeyi test ettim ve yine de performans tamam. Bence Serj-Tm'in çözümü daha basit ve daha iyi korunabilirliğe sahip.
Sessiz Sojourner

2
S'yi bırakmak ToList()ve tembel değerlendirmenin sihir yapmasına izin vermek en iyisi olabilir .
Yair Halberstadt

3
@DmitryPavlov sırasında tüm bu kez, bir select deyimi böyle endeksi proje edememek hakkında bilmiyordum! Bunu 2014'te yayınladığınızı fark edene kadar yeni bir özellik olduğunu düşündüm, bu beni gerçekten şaşırttı! Bunu paylaştığın için teşekkürler. Ayrıca, bu uzantı yönteminin bir IEnumerable için kullanılabilir olması ve ayrıca bir IEnumerable döndürmesi daha iyi olmaz mıydı?
Aydın

37

nasıl:

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

Bu çok fazla bellek tüketecek mi? Locations.Skip.ToList her seferinde daha fazla belleğin ayrılıp ayrılmadığını ve eşlenmemiş öğelerin yeni bir liste tarafından kaynaklanıp kaynaklanmadığını merak ediyorum.
Zasz

2
evet her döngüde yeni liste oluşturulur. Evet bellek tüketir. Ancak bellek sorunları yaşıyorsanız, bu listelerin örnekleri bir sonraki döngüde toplanmaya hazır olduğu için burası optimize edilecek yer değildir. Atlayarak bellek için performans ticaret yapabilirsiniz ToListama ben optimize etmeye çalışırken rahatsız olmaz - çok önemsiz ve olası bir darboğaz. Bu uygulamanın ana kazancı, anlaşılması kolay önemsizliğidir. İsterseniz kabul edilen yanıtı kullanabilirsiniz, bu listeleri oluşturmaz ama biraz daha karmaşıktır.
Rafal

2
.Skip(n)nher çağrıldığında öğeler üzerinde yineleme yapar , ancak bu uygun olsa da, performans açısından kritik kodları dikkate almak önemlidir. stackoverflow.com/questions/20002975/…
Chakrava

@Chakrava emin, benim çözüm performans kritik kod kullanılmayacak, ama benim deneyimime göre önce çalışma kodu yazmak ve daha sonra performans kritik nedir ve nadiren nerede nesneler için linq 50 nesneler üzerinde gerçekleştirilen nerede belirlemek. Bu durum duruma göre değerlendirilmelidir.
Rafal

@Rafal Katılıyorum, .Skip()şirketimin kod tabanında çok sayıda s buldum ve "optimal" olmasalar da gayet iyi çalışıyorlar. DB işlemleri gibi şeyler zaten çok daha uzun sürer. Ama bence .Skip(), n'inci elemente doğrudan atlamak yerine (beklediğiniz gibi) her bir öğeyi <n "yolda" dokunduğunu unutmamak önemlidir . Yineleyicinizin bir öğeye dokunmanın yan etkileri varsa, .Skip()bulunması zor hataların nedeni olabilir.
Chakrava

11

Serj-Tm çözümü gayet iyi, ayrıca bu listeler için genişletme yöntemi olarak genel bir sürüm (statik bir sınıfa koy):

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

Kabul edilen cevabı (Serj-Tm) en sağlam buluyorum, ancak genel bir versiyon önermek istiyorum.

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

8

Library MoreLinq adlı yöntem var 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++;
}

Sonuç:

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 2 elemanlı 5 parçaya bölünür.


Bunun kabul edilen cevap olması gerekir. Veya bu sayfada en azından çok daha yüksek.
Zar Shardan

7

Herhangi bir tür float dahil alacak genel bir yöntem var ve birim test edilmiş, yardımcı olur umarım:

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

Teşekkürler. Yorumları maxCount parametre tanımıyla güncelleyip güncelleştiremeyeceğinizi merak ediyor musunuz? Güvenlik ağı mı?
Andrew Jens

2
numaralandırılabilenlerin birden fazla numaralandırmasına dikkat edin. values.Count()önce tam bir numaralandırmaya ve sonra values.ToList()başka bir numaralandırmaya neden olur . Bunu yapmak daha güvenlidir, values = values.ToList()zaten gerçekleşmiştir.
mhand

7

Yukarıdaki cevapların çoğu işi yaparken, hepsi bitmeyen bir dizide (veya gerçekten uzun bir dizide) korkunç bir şekilde başarısız olurlar. Aşağıdakiler, mümkün olan en iyi zaman ve bellek karmaşıklığını garanti eden tamamen çevrimiçi bir uygulamadır. Sadece bir kez numaralandırılabilen kaynağı tekrarlıyoruz ve tembel değerlendirme için verim getirisini kullanıyoruz. Tüketici, her yinelemede listeyi atabilir, böylece bellek alanı batchSize, öğe sayısı w / listeninkine eşit olur .

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

DÜZENLEME: Şu anda OP'nin List<T>daha küçük bir parçaya bölünmesini istediğini fark List<T>ettim, bu yüzden sonsuz numaralandırılabilirlerle ilgili yorumlarım OP için geçerli değil, ancak burada kalanlara yardımcı olabilir. Bu yorumlar, IEnumerable<T>işlevlerine girdi olarak kullanılan ancak kaynağı birden çok kez numaralandıran diğer yayınlanan çözümlere yanıt olarak verilmiştir .


Ben IEnumerable<IEnumerable<T>>çok fazla Listinşaat içermez çünkü sürümü daha iyi olduğunu düşünüyorum .
NetMage

@NetMage - bir sorun IEnumerable<IEnumerable<T>>, uygulamanın verilecek her iç numaralandırmayı tam olarak numaralandıran tüketiciye dayanmasıdır . Bir çözüm bu sorunu önlemek için bir şekilde ifade edilebilir eminim, ama sonuçta kodu oldukça hızlı bir şekilde karmaşık olabileceğini düşünüyorum. Ayrıca, tembel olduğu için, her seferinde sadece tek bir liste oluşturuyoruz ve ön bellek boyutunu bildiğimiz için bellek tahsisi liste başına tam olarak bir kez gerçekleşiyor.
mhand

Haklısın - uygulamam standart bir numaralandırıcıyı saran mevcut konumunuzu izleyen yeni bir numaralandırıcı (Konum Numaralandırıcı) kullanıyor ve yeni bir konuma geçmenizi sağlar.
NetMage

6

Sonunda mhand çok yararlı bir yorumdan sonra ekleme

Orijinal cevap

Çoğu çözüm işe yarasa da, bence çok verimli değiller. İlk birkaç parçanın yalnızca ilk birkaç öğesini istiyorsanız varsayalım. O zaman dizinizdeki tüm (milyon) öğeyi yinelemek istemezsiniz.

Aşağıdakiler en fazla iki kez numaralandırılacaktır: biri Take için, diğeri Skip için. Kullanacağınızdan daha fazla öğe üzerinde numaralandırılmayacaktır:

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

Bu diziyi kaç kez numaralandırır?

Varsayalım ki kaynağınız chunkSize. Sadece ilk N parçasını numaralandırıyorsunuz. Her numaralandırılmış parçadan yalnızca ilk M öğelerini numaralandıracaksınız.

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

Herhangi biri, Numaralandırıcıyı alır, 1 MoveNext () işlemi yapar ve Numaralandırıcıyı Ortadan Kaldırdıktan sonra döndürülen değeri döndürür. Bu N kez yapılacak

yield return source.Take(chunkSize);

Göre referans kaynağı böyle bir şey yapar:

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

Getirilen Chunk üzerinde numaralandırmaya başlayıncaya kadar bu pek bir şey yapmaz. Birkaç parça alırsanız, ancak ilk yığın üzerinde numaralandırmamaya karar verirseniz, hata ayıklayıcının size göstereceği için foreach yürütülmez.

İlk yığının ilk M öğelerini almaya karar verirseniz, verim dönüşü tam olarak M kez yürütülür. Bu şu anlama gelir:

  • numaralandırıcıyı al
  • MoveNext () ve Current M sürelerini çağırın.
  • Numaralandırıcıyı atın

İlk yığın geri getirildikten sonra, bu ilk yığın'ı atlarız:

source = source.Skip(chunkSize);

Yine kez: biz bakacağız referans kaynağı bulmak içinskipiterator

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

Gördüğünüz gibi , Chunk'taki her öğe için SkipIteratorçağrılar bir MoveNext()kez. AramıyorCurrent .

Yani Chunk'a göre aşağıdakilerin yapıldığını görüyoruz:

  • Herhangi biri (): GetEnumerator; 1 MoveNext (); Numaralandırıcıyı atın;
  • ) (Atın:

    • yığın içeriği numaralandırılmazsa hiçbir şey.
    • İçerik numaralandırılırsa: GetEnumerator (), numaralandırılmış öğe başına bir MoveNext ve bir Current, numaralandırıcıyı imha et;

    • Skip (): numaralandırılan her yığın için (yığının içeriği DEĞİL): GetEnumerator (), MoveNext () chunkSize kez, Current yok! Numaralandırıcıyı atın

Sayıcıda neler olduğuna bakarsanız, MoveNext () 'e çok sayıda çağrı olduğunu görürsünüz ve yalnızca Current gerçekten erişmeye karar verdiğiniz TSource öğeleri için yaparsınız.

ChunkSize boyutunda N Chunks alırsanız, MoveNext () işlevini çağırır

  • Herhangi biri için N kez ()
  • Topaklar için numaralandırmadığınız sürece Take için henüz zaman yok
  • N kere chunkSize for Skip ()

Her getirilen yığının yalnızca ilk M öğelerini numaralandırmaya karar verirseniz, numaralandırılmış Chunk başına MoveNext M kez çağırmanız gerekir.

Toplam

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

Dolayısıyla, tüm parçaların tüm öğelerini numaralandırmaya karar verirseniz:

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

MoveNext'in çok çalışıp çalışmadığı kaynak dizisinin türüne bağlıdır. Listeler ve diziler için, aralık dışı bir kontrolle basit bir dizin artışıdır.

Ancak IEnumerable'iniz bir veritabanı sorgusunun sonucuysa, verilerin gerçekten bilgisayarınızda gerçekleştiğinden emin olun, aksi takdirde veriler birkaç kez alınır. DbContext ve Dapper, erişilmeden önce verileri yerel sürece doğru şekilde aktarır. Aynı diziyi birkaç kez numaralandırırsanız, birkaç kez getirilmez. Dapper Liste olan bir nesneyi döndürür, DbContext verinin önceden getirildiğini hatırlar.

Parçaları Chunks'ta bölmeye başlamadan önce AsEnumerable () veya ToLists () öğesini çağırmanın akıllıca olup olmadığı Havuzunuza bağlıdır.


Bu parti başına iki kez numaralandırılmayacak mı? yani kaynak 2*chunkSizezamanlarını gerçekten sıralıyoruz ? Bu, numaralandırılabilir olanın kaynağına (belki de DB destekli veya başka bir not edilmemiş kaynak) bağlı olarak ölümcüldür. Bu sayılabilir bir girdi olarak düşünün Enumerable.Range(0, 10000).Select(i => DateTime.UtcNow)- numaralandırılmadığı için numaralandırılabilir her numaralandırdığınızda farklı zamanlar alacaksınız
mhand

Düşünün: Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). Çağırarak Anygeçerli saati her zaman recomputing olacak. Çok kötü değil DateTime.UtcNow, ama bir veritabanı bağlantısı / sql imleci veya benzeri tarafından desteklenen bir numaralandırmayı düşünün. Binlerce DB çağrısının yayınlandığı vakaları gördüm çünkü geliştirici 'numaralandırmanın birden fazla numaralandırmasının' potansiyel yansımalarını anlamadı - ReSharper bunun için de bir ipucu veriyor
mhand

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

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

2

Buna ne dersin? Fikir sadece bir döngü kullanmaktı. Ve kim bilir, belki sadece IList uygulamalarını kodunuzu eksiksiz kullanırsınız ve Listeye yayınlamak istemezsiniz.

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

Bir tane daha

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

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

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

2
kod pasajı sağlamak yerine cevabınızı açıklamak tercih edilir
Kevin

0

Aynı ihtiyacı karşıladım ve Linq's Skip () ve Take () yöntemlerinin bir kombinasyonunu kullandım . Aldığım sayıyı şimdiye kadar yineleme sayısıyla çarpıyorum ve bu bana atlamam gereken öğelerin sayısını veriyor, sonra bir sonraki grubu alıyorum.

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

0

Dimitry Pavlov cevabını temel alarak kaldırırdım .ToList(). Ayrıca anonim sınıftan kaçının. Bunun yerine bir yığın bellek ayırma gerektirmeyen bir yapı kullanmak istiyorum. (A ValueTupleda iş yapar.)

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

Bu, yalnızca koleksiyonun üzerinde bir kez yinelenen ve ayrıca önemli bir bellek ayırmayan aşağıdaki gibi kullanılabilir.

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

Somut bir listeye gerçekten ihtiyaç duyulursa, bunu şöyle yaparım:

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.