리스트를 더 작은 N 크기의리스트로 분할


209

목록을 일련의 작은 목록으로 나누려고합니다.

내 문제 : 목록을 분할하는 기능이 올바른 크기의 목록으로 분할되지 않습니다. 그것들을 크기 30의 목록으로 나누어야하지만 대신 크기 114의 목록으로 나누어야합니까?

함수를 사용하여 목록을 30 개 이하 의 X 개 목록으로 나누려면 어떻게 해야합니까?

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 크기의 목록에서 함수를 사용하면 출력은 다음과 같습니다.

색인 : 4, 크기 : 120
색인 : 3, 크기 : 114
색인 : 2, 크기 : 114
색인 : 1, 크기 : 114
색인 : 0, 크기 : 114


1
LINQ 솔루션이 수용 가능한 경우이 질문이 도움이 될 수 있습니다 .

특히 이전 질문에 대한 Sam Saffron의 답변입니다. 그리고 이것이 학교 과제를위한 것이 아니라면 나는 그의 코드를 사용하고 그만 둘 것입니다.
jcolebrand

답변:


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

일반 버전 :

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

따라서 List length zillion이 있고 더 작은 List Length 30으로 나누고 작은리스트에서 Take (1) 만 원하면 30 개 항목의 목록을 작성하고 29 개 항목을 버립니다. 더 똑똑하게 할 수 있습니다!
Harald Coppoolse

이것이 실제로 작동합니까? nSize에서 nSize의 범위를 가져 오기 때문에 첫 번째 분할에서 실패하지 않습니까? 예를 들어 nSize가 3이고 내 배열의 크기가 5 인 경우 반환되는 첫 번째 인덱스 범위는 다음과 같습니다.GetRange(3, 3)
Matthew Pigram

2
@MatthewPigram이 테스트되었으며 작동 중입니다. Math.Min은 최소값을 취하므로 마지막 청크가 nSize (2 <3)보다 작은 경우 나머지 항목이있는 목록을 만듭니다.
Phate01

1
@HaraldCoppoolse OP는 목록을 분할하기 위해서만 선택하도록 요청하지 않았습니다
Phate01

@MatthewPigram 첫 번째 반복-GetRange (0,3), 두 번째 반복-GetRange (3,2)
Serj-Tm

381

이 확장 방법을 사용하여 지정된 청크 크기로 소스 목록을 하위 목록에 청크하는 것이 좋습니다.

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

예를 들어, 청크 당 5 개 항목으로 18 개 항목 목록을 청크하면 5-5-5-3 안에 다음 항목이 포함 된 4 개의 하위 목록이 표시됩니다.


25
프로덕션 환경에서이 기능을 사용하기 전에 메모리 및 성능에 대한 런타임 영향을 이해해야합니다. LINQ가 간결 할 수 있다고해서 좋은 생각은 아닙니다.
Nick

4
분명히, @Nick 나는 일반적으로 무언가를하기 전에 생각하는 것이 좋습니다. LINQ로 청크 작업을 자주 반복하여 수천 번 반복해서는 안됩니다. 일반적으로 일괄 처리 및 / 또는 병렬로 항목을 처리하기 위해 목록을 청크해야합니다.
Dmitry Pavlov

6
메모리와 성능이 큰 문제라고 생각하지 않습니다. 200,000 개가 넘는 레코드가있는 목록을 각각 약 3000 개의 작은 목록으로 나누는 요구가 있어서이 스레드로 나를 데려 갔고 두 방법을 테스트했으며 실행 시간이 거의 동일하다는 것을 알았습니다. 그 후 나는 그 목록을 각각 3 개의 레코드가있는 목록으로 나누는 것을 테스트했지만 여전히 성능은 괜찮습니다. Serj-Tm의 솔루션이 더 간단하고 유지 관리 성이 뛰어나다 고 생각합니다.
Silent Sojourner

2
ToList()s 를 떠나는 것이 가장 좋으며 게으른 평가로 마술처럼 할 수 있습니다.
Yair Halberstadt

3
@DmitryPavlov이 모든 시간 동안, 나는 select 문에서 이와 같은 인덱스를 투영 할 수있는 방법에 대해 전혀 몰랐습니다! 나는 당신이 2014 년에 이것을 게시 한 것을 알 때까지 새로운 기능이라고 생각했습니다. 이것을 공유해 주셔서 감사합니다. 또한이 확장 방법을 IEnumerable에서 사용할 수있게하고 IEnumerable을 반환하는 것이 더 나쁘지 않습니까?
Aydin

37

어때요?

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

이것이 많은 메모리를 소비합니까? location.Skip.ToList가 발생할 때마다 더 많은 메모리가 할당되고 스냅되지 않은 항목이 새 목록으로 참조되는지 궁금합니다.
Zasz

2
예, 모든 루프에서 새 목록이 생성됩니다. 예, 메모리를 소비합니다. 그러나 메모리 문제가있는 경우 다음 루프에서 해당 목록의 인스턴스를 수집 할 수 있으므로 최적화 할 수 없습니다. 건너 뛰면 메모리 성능을 교환 할 수 ToList있지만 최적화하려고는하지 않습니다. 사소하고 병목 현상이 발생하지 않습니다. 이 구현의 주요 이점은 이해하기 쉬운 사소한 것입니다. 허용 된 답변을 사용할 수 있으면 원하는 목록을 만들지 않지만 조금 더 복잡합니다.
Rafal

2
.Skip(n)n호출 될 때마다 요소를 반복 하지만, 괜찮을 수도 있지만 성능에 중요한 코드를 고려해야합니다. stackoverflow.com/questions/20002975/…
차크라 바

@ Chakrava, 내 솔루션은 성능 결정 코드에서 사용되지 않지만 내 경험상 먼저 작업 코드를 작성한 다음 성능 결정이 무엇인지 결정하고 50 개의 객체에서 객체 작업에 대한 내 linq를 거의 수행하지 않습니다. 사례별로 평가해야합니다.
Rafal

@Rafal 나는 동의한다. 나는 .Skip()우리 회사의 코드베이스에서 수많은 것들을 발견했으며 , "최적의"것은 아니지만 잘 작동한다. 어쨌든 DB 작업과 같은 것들이 훨씬 오래 걸립니다. 그러나 나는 .Skip()n 번째 요소로 직접 이동하는 대신 각 요소 <n 을 "만지는" 것에 주목하는 것이 중요하다고 생각합니다 (예상 한 것처럼). 반복자가 요소를 만져서 부작용 .Skip()이있는 경우 찾기 어려운 버그의 원인이 될 수 있습니다.
차크라 바

11

Serj-Tm 솔루션은 괜찮습니다. 또한 이것은 목록의 확장 메소드로서 일반 버전입니다 (정적 클래스에 넣습니다).

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

허용되는 답변 (Serj-Tm)이 가장 강력하지만 일반적인 버전을 제안하고 싶습니다.

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

라이브러리 MoreLinq에는 다음과 같은 메소드가 있습니다. 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++;
}

결과는

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 개의 요소로 5 개의 청크로 분할됩니다.


이것은 정답이어야합니다. 아니면 적어도이 페이지에서 더 높은 곳입니다.
Zar Shardan

7

float을 포함하여 모든 유형을 취하는 일반적인 방법이 있으며 단위 테스트를 거쳤으므로 도움이되기를 바랍니다.

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

감사. maxCount 매개 변수 정의로 주석을 업데이트 할 수 있는지 궁금하십니까? 안전망?
Andrew Jens

2
열거 형의 여러 열거에주의하십시오. values.Count()전체 열거와 values.ToList()다른 열거를 유발합니다 . values = values.ToList()그렇게하는 것이 더 안전합니다 .
mhand

7

위의 많은 답변이 일을하지만, 끝없는 시퀀스 (또는 정말로 긴 시퀀스)에서 모두 실패합니다. 다음은 가능한 최고의 시간과 메모리 복잡성을 보장하는 완전 온라인 구현입니다. 열거 가능한 소스를 정확히 한 번만 반복하고 지연 평가에 수율 반환을 사용합니다. 소비자는 각 반복에서 목록을 버리고 메모리 풋 프린트 batchSize는 요소 수를 가진 목록의 메모리 풋 프린트와 동일하게 만듭니다 .

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

편집 : 방금 OP List<T>를 작은 것을 나누는 것에 대한 요구를 깨닫기 List<T>때문에 무한 열거 형에 대한 나의 의견은 OP에 적용되지 않지만 여기에 오는 다른 사람들을 도울 수 있습니다. 이 의견은 기능에 대한 IEnumerable<T>입력으로 사용 되지만 소스 열거 가능한 여러 번 열거하는 다른 게시 된 솔루션에 대한 답변 입니다.


나는 IEnumerable<IEnumerable<T>>너무 많은 List구성을 포함하지 않기 때문에 버전이 더 낫다고 생각합니다 .
NetMage

@NetMage-한 가지 문제 IEnumerable<IEnumerable<T>>는 구현이 각 내부 열거 가능한 수율을 완전히 열거하는 소비자에 의존 할 가능성이 있다는 것입니다. 그 문제를 피할 수있는 방법으로 솔루션을 표현할 수는 있지만 결과 코드가 매우 복잡해질 수 있다고 생각합니다. 또한 게으 르기 때문에 한 번에 하나의 목록 만 생성하고 메모리 할당은 목록 당 한 번만 크기를 알기 때문에 목록 당 정확히 한 번 발생합니다.
mhand

당신은 옳습니다-내 구현은 표준 열거자를 감싸는 현재 위치를 추적하는 새로운 유형의 열거 자 (위치 열거 자)를 사용하고 새로운 위치로 옮길 수 있습니다.
NetMage

6

끝에 mhand에 대한 매우 유용한 주석 후 추가

원래 답변

대부분의 솔루션이 작동 할 수 있지만 효율적이지 않다고 생각합니다. 처음 몇 개의 청크 중 처음 몇 개만 원한다고 가정하십시오. 그런 다음 시퀀스의 모든 (거대한) 항목을 반복하고 싶지 않습니다.

다음은 최대한 두 번 열거됩니다. 테이크와 스킵에 대해 한 번. 사용할 것보다 많은 요소를 열거하지 않습니다.

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

시퀀스를 몇 번이나 열거합니까?

소스를의 덩어리로 나눈다 고 가정합니다 chunkSize. 첫 번째 N 청크 만 열거합니다. 열거 된 모든 청크에서 첫 번째 M 요소 만 열거합니다.

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

Any는 열거자를 가져오고 1 MoveNext ()를 수행하고 열거자를 폐기 한 후 반환 된 값을 반환합니다. 이것은 N 번 수행됩니다

yield return source.Take(chunkSize);

참조 소스 에 따르면 다음과 같은 작업을 수행합니다.

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

가져온 청크에 대해 열거하기 시작할 때까지이 작업은 많이 수행되지 않습니다. 여러 청크를 가져 오지만 첫 번째 청크를 열거하지 않기로 결정하면 디버거가 표시하는 것처럼 foreach가 실행되지 않습니다.

첫 번째 청크의 첫 번째 M 요소를 사용하기로 결정하면 수율 반환은 정확히 M 번 실행됩니다. 이것은 다음을 의미합니다.

  • 열거자를 얻는다
  • MoveNext () 및 현재 M 번을 호출하십시오.
  • 열거자를 폐기하십시오

첫 번째 청크가 반환 된 후 첫 번째 청크를 건너 뜁니다.

source = source.Skip(chunkSize);

다시 한 번 : 참조 소스 를 살펴보고skipiterator

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

보시다시피 청크의 모든 요소에 대해 한 번씩 SkipIterator호출 MoveNext()됩니다. 호출하지 않습니다 Current.

따라서 청크마다 다음이 완료되었음을 알 수 있습니다.

  • Any () : GetEnumerator; 1 MoveNext (); 열거자를 처리하십시오.
  • 취하다():

    • 청크의 내용이 열거되지 않으면 아무것도 아닙니다.
    • 내용이 열거 된 경우 : GetEnumerator (), 열거 된 항목 당 하나의 MoveNext 및 하나의 현재 항목, Dispose 열거 자;

    • Skip () : 열거 된 모든 청크에 대해 (청크 내용이 아님) : GetEnumerator (), MoveNext () chunkSize times, no Current! 열거 자 처리

열거 자에 어떤 일이 발생하는지 살펴보면 MoveNext ()에 대한 많은 호출이 있고 Current실제로 액세스하기로 결정한 TSource 항목 에 대한 호출 만 있음 을 알 수 있습니다.

chunkSize 크기의 N 청크를 사용하는 경우 MoveNext ()를 호출합니다.

  • Any ()에 대해 N 회
  • 청크를 열거하지 않는 한 테이크 시간은 아직 없습니다.
  • Skip ()에 대해 N 배 chunkSize

페치 된 모든 청크의 첫 번째 M 요소 만 열거하기로 결정한 경우 열거 된 청크 당 MoveNext M 시간을 호출해야합니다.

전체

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

따라서 모든 청크의 모든 요소를 ​​열거하기로 결정한 경우 :

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

MoveNext의 작업 여부는 소스 시퀀스 유형에 따라 다릅니다. 목록 및 배열의 ​​경우 범위를 벗어난 점검으로 단순한 인덱스 증분입니다.

그러나 IEnumerable이 데이터베이스 쿼리의 결과 인 경우 데이터가 실제로 컴퓨터에서 구체화되어 있는지 확인하십시오. 그렇지 않으면 데이터가 여러 번 페치됩니다. DbContext 및 Dapper는 액세스하기 전에 데이터를 로컬 프로세스로 올바르게 전송합니다. 동일한 시퀀스를 여러 번 열거하면 여러 번 페치되지 않습니다. Dapper는 List 인 객체를 반환하고 DbContext는 데이터를 이미 가져온 것을 기억합니다.

청크에서 항목을 나누기 전에 AsEnumerable () 또는 ToLists ()를 호출하는 것이 현명한 지 여부는 리포지토리에 따라 다릅니다.


이것은 배치 두 번 열거되지 않습니까? 소스 2*chunkSize시간을 실제로 열거하고 있습니까? 열거 가능한 소스 (아마도 DB 지원 또는 기타 비 메모리 소스)에 따라 치명적입니다. 이 열거 형을 입력으로 상상해보십시오. 열거 Enumerable.Range(0, 10000).Select(i => DateTime.UtcNow)되지 않은 열거 형을 열거 할 때마다 시간이 다르게
나타날 것입니다.

고려하십시오 : Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). 호출 Any하면 매번 현재 시간을 다시 계산하게됩니다. 에 대한 나쁘지는 DateTime.UtcNow않지만 데이터베이스 연결 / SQL 커서 또는 이와 유사한 것으로 뒷받침되는 열거 가능한 것을 고려하십시오. 나는 개발자가 '열거 가능한 여러 열거'의 잠재적 영향을 이해하지 못하기 때문에 DB 호출 수천이 발행 된 경우를 본 적이 - ReSharper에서가 아니라 이것에 대한 힌트를 제공합니다
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

이건 어때? 아이디어는 하나의 루프 만 사용하는 것이 었습니다. 그리고 당신은 알고 있습니다. 아마도 코드를 통해 IList 구현 만 사용하고 List로 캐스팅하고 싶지 않을 것입니다.

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

하나 더

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
코드 스 니펫 만 제공하는 것이 아니라 답변을 설명하는 것이 좋습니다
Kevin

0

나는 이와 동일한 요구를 겪었고 Linq의 Skip ()Take () 메소드 의 조합을 사용했습니다 . 여기까지의 반복 횟수에 곱한 수를 곱하면 건너 뛸 항목 수를 제공 한 다음 다음 그룹을 사용합니다.

        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

를 기반으로 드미트리 파블로프는 answere 내가 제거 할 것 .ToList(). 또한 익명 클래스를 피하십시오. 대신 힙 메모리 할당이 필요없는 구조체를 사용하고 싶습니다. (A ValueTuple도 일을 할 것입니다.)

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

이것은 컬렉션처럼 한 번만 반복하고 중요한 메모리를 할당하지 않는 다음과 같이 사용할 수 있습니다.

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

구체적인 목록이 실제로 필요한 경우 다음과 같이하십시오.

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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.