source

목록을 N개 크기의 작은 목록으로 분할

manysource 2023. 4. 15. 09:03

목록을 N개 크기의 작은 목록으로 분할

목록을 일련의 작은 목록으로 분할하려고 합니다.

문제:목록을 분할하는 기능은 목록을 올바른 크기의 목록으로 분할하지 않습니다.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 : 4, 크기: 120
3, 114 : 3, 크기: 114
2, 114 : 2, 크기: 114
1, 114 : 1, 크기: 114
0, 114 : 0, 크기: 114

다음 확장 메서드를 사용하여 지정된 청크 크기별로 소스 목록을 하위 목록에 청크할 것을 권장합니다.

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

예를 들어 18개 항목 목록을 청크당 5개 항목씩 청크하면 5-5-5-3 항목이 포함된 4개 하위 목록 목록이 표시됩니다.

메모: 청킹의 향후 개선점에서는 다음과 같이 개봉됩니다.

const int PAGE_SIZE = 5;

IEnumerable<Movie[]> chunks = movies.Chunk(PAGE_SIZE);
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)); 
    }  
} 

어때?

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

라이브러리 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

ids2시 5분

의 갱신을 실시.넷 6

var originalList = new List<int>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}

// split into arrays of no more than three
IEnumerable<int[]> chunks = originalList.Chunk(3);

에 앞서.넷 6

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

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

인정된 답변(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;
}

마지막에 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는 Enumerator를 가져와 1 MoveNext()를 실행하고 Enumerator Dispatching 후 반환된 값을 반환합니다.이 작업은 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;
    }
}

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

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

  • 조사원을 만나다
  • MoveNext()와 Current 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()청크의 모든 요소에 대해 한 번.호출이 안 돼요.

따라서 청크별로 다음 작업이 완료되었음을 알 수 있습니다.

  • Any(): GetEnumerator, 1 MoveNext(), Dispose Enumerator;
  • 테이크():

    • 청크의 내용이 열거되지 않으면 아무것도 표시되지 않습니다.
    • 내용이 열거된 경우:열거된 항목당 GetEnumerator(), MoveNext 1개 및 Current 1개, Dispose Enumerator.

    • Skip(): 열거되는 모든 청크에 대해(청크 내용이 아님):GetEnumerator(), MoveNext() 청크 횟수, 현재 없음!열거자 폐기

대해 MoveNext MoveNext()에 대한 을 알 수 CurrentTSource.

청크 사이즈의 N개의 청크를 취득하면는 MoveNext()에 콜합니다.

  • Any()의 N회
  • Chunks를 열거하지 않는 한 Take를 위한 시간은 아직 없습니다.
  • N 곱하기 Skip()의 청크 크기

가져온 각 청크의 첫 번째 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는 목록인 개체를 반환하고, DbContext는 데이터가 이미 가져온 것을 기억합니다.

Chunks로 항목을 분할하기 전에 AsEnumable() 또는 ToLists() 중 어느 쪽을 호출하는 것이 현명한지에 따라 달라집니다.

위의 많은 답변들이 효과가 있지만, 그것들은 모두 끝없는 시퀀스(또는 정말 긴 시퀀스)에서 끔찍하게 실패한다.다음은 시간과 메모리의 복잡성을 최대한으로 보장하는 완전한 온라인 구현입니다.우리는 정확히 한 번만 열거할 수 있는 출처를 반복하고 게으른 평가를 위해 수익률을 사용한다.소비자는 반복할 때마다 목록을 폐기할 수 있으며 메모리 용량은 목록과 동일합니다.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가 Breaking에 대해 묻습니다.List<T>작게List<T>따라서 무한 열거 가능에 대한 제 의견은 OP에는 적용되지 않지만, 여기에 있는 다른 사람들에게 도움이 될 수 있습니다.이러한 코멘트는, 다음과 같은 기능을 사용하는 다른 투고된 솔루션에 대한 응답입니다.IEnumerable<T>그 함수에 대한 입력으로, 그러나 여러 번 열거할 수 있는 소스를 열거합니다.

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

.NET 6.0 이후로는 LINQ 확장자를 사용할 수 있습니다.Chunk<T>()열거형을 청크로 분할합니다.문서

var chars = new List<char>() { 'h', 'e', 'l', 'l', 'o', 'w','o','r' ,'l','d' };
foreach (var batch in chars.Chunk(2))
{
    foreach (var ch in batch)
    {
        // iterates 2 letters at a time
    }
}
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));
}

이것은 어떠세요?그 아이디어는 하나의 루프만 사용하는 것이었습니다.또한 코드를 통해 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;
            }
        }
    }   
}

.NET 6 에서는, 다음의 명령어를 사용할 수 있습니다.source.Chunk(chunkSize)

Serj-Tm에 의해 인정된 답변에 근거한 보다 일반적인 버전.

    public static IEnumerable<IEnumerable<T>> Split<T>(IEnumerable<T> source, int size = 30)
    {
        var count = source.Count();
        for (int i = 0; i < count; i += size)
        {
            yield return source
                .Skip(Math.Min(size, count - i))
                .Take(size);
        }
    }

하나 더

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

같은 요구를 충족시켜 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;
        }

디미트리 파블로프 앤워에 따르면.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);
}
List<int> orginalList =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 = orginalList.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<orginalList.Count; i++)
{
lst.Add(orginalList[i]);
if (i % batchCount == 0 && i!=0)
{
Dic.Add(threadId, lst);
lst = new List<int>();**strong text**
threadId++;
}
}
if(lst.Count>0)
Dic.Add(threadId, lst); //in case if any dayleft 
foreach(int BatchId in Dic.Keys)
{
  Console.Writeline("BatchId:"+BatchId);
  Console.Writeline('Batch Count:"+Dic[BatchId].Count);
}

고정수가 아닌 조건으로 분할하는 경우:

///<summary>
/// splits a list based on a condition (similar to the split function for strings)
///</summary>
public static IEnumerable<List<T>> Split<T>(this IEnumerable<T> src, Func<T, bool> pred)
{
    var list = new List<T>();
    foreach(T item in src)
    {   
        if(pred(item))
        {
            if(list != null && list.Count > 0)
                yield return list;
                
            list = new List<T>();
        }
        else
        {
            list.Add(item);
        }
    }
}

LINQ 를 사용하는 것만으로, 다음의 코드를 시험할 수 있습니다.

public static IList<IList<T>> Split<T>(IList<T> source)
{
    return  source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / 3)
        .Select(x => x.Select(v => v.Value).ToList())
        .ToList();
}

언급URL : https://stackoverflow.com/questions/11463734/split-a-list-into-smaller-lists-of-n-size