Chia danh sách thành các danh sách nhỏ hơn có kích thước N


209

Tôi đang cố gắng chia một danh sách thành một loạt các danh sách nhỏ hơn.

Vấn đề của tôi: Chức năng phân chia danh sách của tôi không phân chia chúng thành các danh sách có kích thước chính xác. Nó nên chia chúng thành các danh sách kích thước 30 nhưng thay vào đó nó chia chúng thành các danh sách kích thước 114?

Làm cách nào tôi có thể làm cho chức năng của mình chia một danh sách thành X số Danh sách có kích thước từ 30 trở xuống ?

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

Nếu tôi sử dụng hàm trên danh sách kích thước 144 thì đầu ra là:

Chỉ số: 4, Kích thước: 120
Chỉ số: 3, Kích thước: 114
Chỉ số: 2, Kích thước: 114
Chỉ số: 1, Kích thước: 114
Chỉ số: 0, Kích thước: 114


1
Nếu một giải pháp LINQ được chấp nhận, câu hỏi này có thể giúp ích .

Cụ thể câu trả lời của Sam Saffron cho câu hỏi trước đó. Và trừ khi điều này là cho một bài tập ở trường, tôi sẽ chỉ sử dụng mã của anh ấy và dừng lại.
jcolebrand

Câu trả lời:


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

Phiên bản chung:

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

Vì vậy, nếu tôi có một phần trăm Độ dài danh sách và tôi muốn chia thành các danh sách nhỏ hơn Độ dài 30 và từ mỗi danh sách nhỏ hơn tôi chỉ muốn lấy (1), thì tôi vẫn tạo danh sách 30 mục mà tôi vứt đi 29 mục. Điều này có thể được thực hiện thông minh hơn!
Harald Coppoolse

Điều này thực sự làm việc? Nó sẽ không thất bại trong lần phân chia đầu tiên bởi vì bạn đang nhận được phạm vi nSize thành nSize? Ví dụ: nếu nSize là 3 và mảng của tôi là kích thước 5 thì phạm vi chỉ mục đầu tiên được trả về làGetRange(3, 3)
Matthew Pigram 22/03/18

2
@MatthewPigram đã thử nghiệm và nó hoạt động. Math.Min lấy giá trị tối thiểu để nếu đoạn cuối nhỏ hơn nSize (2 <3), nó sẽ tạo một danh sách với các mục còn lại.
Phate01

1
@HaraldCoppoolse OP không yêu cầu chọn, chỉ chia nhỏ danh sách
Phate01

@MatthewPigram Lặp lại đầu tiên - GetRange (0,3), lần lặp thứ hai - GetRange (3,2)
Serj-Tm

380

Tôi sẽ đề nghị sử dụng phương pháp tiện ích mở rộng này để phân chia danh sách nguồn vào danh sách phụ theo kích thước khối được chỉ định:

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

Ví dụ: nếu bạn liệt kê danh sách 18 mục cho 5 mục trên mỗi phần, nó sẽ cung cấp cho bạn danh sách 4 danh sách phụ với các mục sau trong: 5-5-5-3.


25
Trước khi bạn sử dụng điều này trong sản xuất, hãy chắc chắn rằng bạn hiểu ý nghĩa của thời gian chạy đối với bộ nhớ và hiệu suất là gì. Chỉ vì LINQ có thể cô đọng, không có nghĩa đó là một ý tưởng hay.
Nick

4
Chắc chắn, @Nick tôi sẽ đề nghị nói chung suy nghĩ trước khi làm bất cứ điều gì. Chunking với LINQ không nên là một hoạt động thường xuyên lặp đi lặp lại hàng ngàn lần. Thông thường, bạn cần phải liệt kê danh sách để xử lý các mục theo từng đợt và / hoặc song song.
Dmitry Pavlov

6
Tôi không nghĩ rằng bộ nhớ và hiệu suất nên là một vấn đề lớn ở đây. Tôi tình cờ có một yêu cầu chia một danh sách với hơn 200.000 bản ghi thành các danh sách nhỏ hơn với khoảng 3000 mỗi danh sách, điều này đưa tôi đến chủ đề này và tôi đã thử nghiệm cả hai phương pháp và thấy thời gian chạy gần như nhau. Sau đó tôi đã thử chia danh sách đó thành các danh sách với 3 bản ghi mỗi bản và hiệu suất vẫn ổn. Tôi nghĩ rằng giải pháp của Serj-Tm đơn giản hơn và có khả năng bảo trì tốt hơn.
Imjourner im lặng

2
Lưu ý rằng tốt nhất là nên rời khỏi ToList()s và để cho việc đánh giá lười biếng thực hiện điều đó thật kỳ diệu.
Yair Halberstadt

3
@DmitryPavlov Trong tất cả các thời gian này, tôi không bao giờ biết về việc có thể để dự kiến chỉ số như vậy trong một tuyên bố chọn! Tôi nghĩ rằng đó là một tính năng mới cho đến khi tôi nhận thấy bạn đăng bài này vào năm 2014, điều đó thực sự làm tôi ngạc nhiên! Cảm ơn đã chia sẻ điều này. Ngoài ra, sẽ tốt hơn nếu có phương thức mở rộng này có sẵn cho IEnumerable và cũng trả lại IEnumerable?
Aydin

37

làm thế nào về:

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

Điều này sẽ tiêu thụ rất nhiều bộ nhớ? Mỗi lần định vị.Skip.ToList xảy ra Tôi tự hỏi liệu có thêm bộ nhớ được phân bổ không và các mục không bị cắt được tham chiếu bởi một danh sách mới.
Zasz

2
có danh sách mới được tạo trên mỗi vòng lặp. Có nó tiêu thụ bộ nhớ. Nhưng nếu bạn gặp vấn đề về bộ nhớ thì đây không phải là nơi để tối ưu hóa vì các phiên bản của danh sách đó đã sẵn sàng để được thu thập ở vòng lặp tiếp theo. Bạn có thể trao đổi hiệu năng cho bộ nhớ bằng cách bỏ qua ToListnhưng tôi sẽ không cố gắng tối ưu hóa nó - điều đó thật tầm thường và khó có thể là một nút cổ chai. Lợi ích chính từ việc thực hiện này là tầm thường của nó rất dễ hiểu. Nếu bạn muốn bạn có thể sử dụng câu trả lời được chấp nhận, nó không tạo ra những danh sách đó nhưng phức tạp hơn một chút.
Rafal

2
.Skip(n)Lặp lại ncác phần tử mỗi lần nó được gọi, trong khi điều này có thể ổn, điều quan trọng là phải xem xét mã quan trọng về hiệu năng. stackoverflow.com/questions/20002975/ cường
Chakrava

@Chakrava chắc chắn, giải pháp của tôi không được sử dụng trong mã quan trọng hiệu năng, nhưng theo kinh nghiệm của tôi, trước tiên bạn viết mã làm việc và sau đó xác định điều gì là hiệu suất quan trọng và nó hiếm khi hoạt động linq của tôi đối với các đối tượng thực hiện trên 50 đối tượng. Điều này nên được đánh giá từng trường hợp.
Rafal

@Rafal Tôi đồng ý, tôi đã tìm thấy nhiều .Skip()s trong cơ sở mã của công ty tôi và trong khi chúng có thể không "tối ưu" thì chúng vẫn hoạt động tốt. Những thứ như hoạt động DB mất nhiều thời gian hơn nữa. Nhưng tôi nghĩ đó là một điều quan trọng cần lưu ý rằng .Skip()"chạm" từng phần tử <n trên đường thay vì nhảy trực tiếp đến phần tử thứ n (như bạn có thể mong đợi). Nếu trình vòng lặp của bạn có tác dụng phụ khi chạm vào một phần tử .Skip()có thể là nguyên nhân gây ra các lỗi khó tìm.
Chakrava

11

Giải pháp Serj-Tm vẫn ổn, đây cũng là phiên bản chung dưới dạng phương thức mở rộng cho các danh sách (đặt nó vào một lớp tĩnh):

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

Tôi thấy câu trả lời được chấp nhận (Serj-Tm) mạnh mẽ nhất, nhưng tôi muốn đề xuất một phiên bản chung.

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

Thư viện MoreLinq có phương thức gọi là 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++;
}

Kết quả là

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 được chia thành 5 khối với 2 yếu tố.


Điều này cần phải là câu trả lời được chấp nhận. Hoặc ít nhất là cao hơn rất nhiều trên trang này.
Zar Shardan

7

Tôi có một phương pháp chung có thể sử dụng bất kỳ loại nào bao gồm float và nó đã được thử nghiệm theo đơn vị, hy vọng nó sẽ giúp:

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

Cảm ơn. Tự hỏi nếu bạn có thể cập nhật các ý kiến ​​với định nghĩa tham số maxCount? Một mạng lưới an toàn?
Andrew Jens

2
hãy cẩn thận với nhiều bảng liệt kê. values.Count()sẽ gây ra một liệt kê đầy đủ và sau đó values.ToList()khác. An toàn hơn để làm values = values.ToList()như vậy nó đã được thực hiện.
bắt tay vào

7

Mặc dù có rất nhiều câu trả lời ở trên thực hiện công việc, nhưng tất cả chúng đều thất bại khủng khiếp trên một chuỗi không bao giờ kết thúc (hoặc một chuỗi thực sự dài). Sau đây là một triển khai hoàn toàn trực tuyến đảm bảo độ phức tạp của bộ nhớ và thời gian tốt nhất có thể. Chúng tôi chỉ lặp lại nguồn liệt kê chính xác một lần và sử dụng lợi nhuận để đánh giá lười biếng. Người tiêu dùng có thể loại bỏ danh sách trên mỗi lần lặp làm cho dung lượng bộ nhớ bằng với danh sách w / batchSizesố phần tử.

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: Vừa mới nhận ra OP hỏi về việc chia List<T>nhỏ hơn List<T>, vì vậy những nhận xét của tôi về số lượng vô hạn không thể áp dụng cho OP, nhưng có thể giúp những người khác kết thúc tại đây. Những bình luận này đã phản ứng với các giải pháp được đăng khác sử dụng IEnumerable<T>làm đầu vào cho chức năng của chúng, nhưng liệt kê nguồn vô số nhiều lần.


Tôi nghĩ rằng IEnumerable<IEnumerable<T>>phiên bản này tốt hơn vì nó không liên quan đến việc Listxây dựng quá nhiều .
NetMage

@NetMage - một vấn đề IEnumerable<IEnumerable<T>>là việc triển khai có thể dựa vào người tiêu dùng liệt kê đầy đủ từng sản phẩm được liệt kê bên trong. Tôi chắc chắn rằng một giải pháp có thể được thực hiện theo cách để tránh vấn đề đó, nhưng tôi nghĩ rằng mã kết quả có thể trở nên phức tạp khá nhanh. Ngoài ra, vì lười biếng, chúng tôi chỉ tạo một danh sách duy nhất tại một thời điểm và phân bổ bộ nhớ xảy ra chính xác một lần cho mỗi danh sách kể từ khi chúng tôi biết kích thước lên phía trước.
mhand

Bạn đã đúng - việc triển khai của tôi sử dụng một loại liệt kê mới (Trình liệt kê vị trí) theo dõi vị trí hiện tại của bạn bao bọc một điều tra viên tiêu chuẩn và cho phép bạn chuyển sang một vị trí mới.
NetMage

6

Ngoài ra sau khi nhận xét rất hữu ích của mhand ở cuối

Câu trả lời gốc

Mặc dù hầu hết các giải pháp có thể hoạt động, tôi nghĩ rằng chúng không hiệu quả lắm. Giả sử nếu bạn chỉ muốn một vài mục đầu tiên của một vài khối đầu tiên. Sau đó, bạn sẽ không muốn lặp lại tất cả (hàng triệu) mục trong chuỗi của mình.

Sau đây sẽ liệt kê tối đa hai lần: một lần cho Lấy và một lần cho Bỏ qua. Nó sẽ không liệt kê bất kỳ yếu tố nào nhiều hơn bạn sẽ sử dụng:

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

Bao nhiêu lần điều này sẽ liệt kê trình tự?

Giả sử bạn chia nguồn của bạn thành nhiều phần chunkSize. Bạn liệt kê chỉ N khối đầu tiên. Từ mỗi khối được liệt kê, bạn sẽ chỉ liệt kê các phần tử M đầu tiên.

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

Bất kỳ sẽ nhận được Trình liệt kê, thực hiện 1 MoveNext () và trả về giá trị được trả về sau khi Loại bỏ Trình liệt kê. Điều này sẽ được thực hiện N lần

yield return source.Take(chunkSize);

Theo nguồn tham khảo này sẽ làm một cái gì đó như:

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

Điều này không làm được gì nhiều cho đến khi bạn bắt đầu liệt kê Chunk tìm nạp. Nếu bạn tìm nạp một vài Chun, nhưng quyết định không liệt kê Chunk đầu tiên, thì foreach không được thực thi, vì trình gỡ lỗi của bạn sẽ hiển thị cho bạn.

Nếu bạn quyết định lấy các phần tử M đầu tiên của đoạn đầu tiên thì lợi nhuận mang lại được thực hiện chính xác M lần. Điều này có nghĩa là:

  • lấy điều tra viên
  • gọi MoveNext () và M lần hiện tại.
  • Vứt bỏ điều tra viên

Sau khi phần đầu tiên đã được trả lại, chúng ta bỏ qua phần đầu tiên này:

source = source.Skip(chunkSize);

Một lần nữa: chúng ta sẽ xem nguồn tham khảo để tìmskipiterator

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

Như bạn thấy, các SkipIteratorcuộc gọi MoveNext()một lần cho mọi phần tử trong Chunk. Nó không gọi Current.

Vì vậy, mỗi Chunk chúng ta thấy rằng những điều sau đây được thực hiện:

  • Bất kỳ (): GetEnumerator; 1 MoveNext (); Vứt bỏ ĐTVN;
  • Lấy():

    • không có gì nếu nội dung của đoạn không được liệt kê.
    • Nếu nội dung được liệt kê: GetEnumerator (), một MoveNext và một Hiện tại cho mỗi mục liệt kê, liệt kê liệt kê;

    • Bỏ qua (): cho mỗi đoạn được liệt kê (KHÔNG phải nội dung của đoạn): GetEnumerator (), MoveNext () chunkSize lần, không có Hiện tại! Vứt bỏ điều tra viên

Nếu bạn nhìn vào những gì xảy ra với điều tra viên, bạn sẽ thấy rằng có rất nhiều cuộc gọi đến MoveNext () và chỉ gọi Currentcho các mục TSource mà bạn thực sự quyết định truy cập.

Nếu bạn lấy N Chunks có kích thước chunkSize, thì hãy gọi tới MoveNext ()

  • N lần cho bất kỳ ()
  • chưa có thời gian cho Take, miễn là bạn không liệt kê Chunks
  • N lần chunkSize cho Skip ()

Nếu bạn quyết định chỉ liệt kê các phần tử M đầu tiên của mỗi phần được tìm nạp, thì bạn cần gọi MoveNext M lần cho mỗi phần được liệt kê.

Tổng số

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

Vì vậy, nếu bạn quyết định liệt kê tất cả các yếu tố của tất cả các khối:

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

MoveNext có nhiều công việc hay không, tùy thuộc vào loại chuỗi nguồn. Đối với danh sách và mảng, đó là một chỉ số tăng đơn giản, có thể là kiểm tra ngoài phạm vi.

Nhưng nếu IEnumerable của bạn là kết quả của truy vấn cơ sở dữ liệu, hãy đảm bảo rằng dữ liệu thực sự được vật chất hóa trên máy tính của bạn, nếu không dữ liệu sẽ được tìm nạp nhiều lần. DbContext và Dapper sẽ chuyển đúng dữ liệu sang quy trình cục bộ trước khi có thể truy cập được. Nếu bạn liệt kê trình tự tương tự nhiều lần, nó không được tìm nạp nhiều lần. Dapper trả về một đối tượng là Danh sách, DbContext nhớ rằng dữ liệu đã được tìm nạp.

Tùy thuộc vào Kho lưu trữ của bạn, liệu có nên gọi AsEnumerable () hoặc ToLists () trước khi bạn bắt đầu phân chia các mục trong Chunks


Sẽ không liệt kê hai lần mỗi lô? Vì vậy, chúng tôi thực sự liệt kê 2*chunkSizethời gian nguồn ? Điều này rất nguy hiểm tùy thuộc vào nguồn của vô số (có lẽ là DB được hỗ trợ hoặc nguồn không ghi nhớ khác). Hãy tưởng tượng số này là đầu vào Enumerable.Range(0, 10000).Select(i => DateTime.UtcNow)- bạn sẽ nhận được các thời điểm khác nhau mỗi khi bạn liệt kê số thứ tự kể từ khi nó không được ghi nhớ
bắt đầu từ

Hãy xem xét : Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). Bằng cách gọi, Anybạn sẽ tính toán lại thời gian hiện tại mỗi lần. Không quá tệ cho DateTime.UtcNow, nhưng hãy xem xét một số lượng được hỗ trợ bởi một con trỏ kết nối cơ sở dữ liệu / tương tự. Tôi đã thấy các trường hợp có hàng ngàn cuộc gọi DB được phát hành vì nhà phát triển không hiểu tác động tiềm tàng của 'nhiều liệt kê của một liệt kê' - ReSharper cung cấp một gợi ý cho điều này cũng như vậy
từ

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

Cái này thì sao? Ý tưởng là chỉ sử dụng một vòng lặp. Và, ai biết được, có thể bạn chỉ sử dụng các triển khai IList xuyên suốt mã của mình và bạn không muốn chuyển sang Danh sách.

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

Một lần nữa

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
tốt hơn là giải thích câu trả lời của bạn thay vì chỉ cung cấp một đoạn mã
Kevin

0

Tôi đã gặp phải nhu cầu tương tự và tôi đã sử dụng kết hợp các phương thức Skip ()Take () của Linq . Tôi nhân số lượng tôi lấy với số lần lặp cho đến nay và điều đó cho tôi số lượng mục cần bỏ qua, sau đó tôi chọn nhóm tiếp theo.

        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

Dựa trên câu trả lời của Dimitry Pavlov tôi sẽ xóa .ToList(). Và cũng tránh các lớp ẩn danh. Thay vào đó tôi thích sử dụng một cấu trúc không yêu cầu cấp phát bộ nhớ heap. (A ValueTuplecũng sẽ làm công việc.)

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

Điều này có thể được sử dụng như sau chỉ lặp lại bộ sưu tập một lần và cũng không phân bổ bất kỳ bộ nhớ quan trọng nào.

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

Nếu một danh sách cụ thể là thực sự cần thiết thì tôi sẽ làm như thế này:

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.