我如何分块一个可枚举的?

Posted

技术标签:

【中文标题】我如何分块一个可枚举的?【英文标题】:how do I chunk an enumerable? 【发布时间】:2012-09-12 13:23:27 【问题描述】:

我需要一个优雅的方法,它接受一个可枚举并获取其中每个相同数量的元素但最后一个元素的可枚举:

public static IEnumerable<IEnumerable<TValue>> Chunk<TValue>(this IEnumerable<TValue> values, Int32 chunkSize)

    // TODO: code that chunks

这是我尝试过的:

    public static IEnumerable<IEnumerable<TValue>> Chunk<TValue>(this IEnumerable<TValue> values, Int32 chunkSize)
    
        var count = values.Count();
        var numberOfFullChunks = count / chunkSize;
        var lastChunkSize = count % chunkSize;
        for (var chunkIndex = 0; chunkSize < numberOfFullChunks; chunkSize++)
        
            yield return values.Skip(chunkSize * chunkIndex).Take(chunkSize);
        
        if (lastChunkSize > 0)
        
            yield return values.Skip(chunkSize * count).Take(lastChunkSize);
        
    

更新 刚刚发现有一个类似的关于拆分列表的话题Split List into Sublists with LINQ

【问题讨论】:

见morelinq的Batch方法 LINQ Partition List into Lists of 8 members的可能重复 Split List into Sublists with LINQ的可能重复 【参考方案1】:

如果内存消耗不是问题,那么像这样?

static class Ex

    public static IEnumerable<IEnumerable<TValue>> Chunk<TValue>(
        this IEnumerable<TValue> values, 
        int chunkSize)
    
        return values
               .Select((v, i) => new v, groupIndex = i / chunkSize)
               .GroupBy(x => x.groupIndex)
               .Select(g => g.Select(x => x.v));
    

否则,您可以使用 yield 关键字获得创意,如下所示:

static class Ex

    public static IEnumerable<IEnumerable<TValue>> Chunk<TValue>(
                    this IEnumerable<TValue> values, 
                    int chunkSize)
    
        using(var enumerator = values.GetEnumerator())
        
            while(enumerator.MoveNext())
            
                yield return GetChunk(enumerator, chunkSize).ToList();
            
        
    

    private static IEnumerable<T> GetChunk<T>(
                     IEnumerator<T> enumerator,
                     int chunkSize)
    
        do
        
            yield return enumerator.Current;
         while(--chunkSize > 0 && enumerator.MoveNext());
    

【讨论】:

我喜欢第二个like,因为它不使用Count。 Enumerable.Range(0, 50).Chunk(10).ToArray()(使用第二个Chunk)给了我50个IEnumerable&lt;int&gt;s,每个都只返回49,这...不是我所期望的。跨度> @Rawling,是的,我在您发表评论并修复之前就意识到了这一点......请参阅第一个循环中的 ToList()。 移除这个 .ToList() 投影。方法将至少快两倍。【参考方案2】:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)

    while (source.Any())
    
        yield return source.Take(chunksize);
        source = source.Skip(chunksize);
    

【讨论】:

会导致源集合的多次枚举,可能是个问题 多重枚举不是采用IEnumerable 的方法的良好属性。如果它使用IList&lt;T&gt;,这个函数会很好,因为调用者会知道它需要对整个集合进行急切的评估。多次枚举或急切地实现 IEnumerable 的任何部分(也许除了第一个块之外)都不好。【参考方案3】:

>= .Net 6

Enumerable.Chunk(IEnumerable, Int32) Method

var list = Enumerable.Range(1, 999);

var chunks = list.Chunk(29);  // <--- here it is :)

foreach(var chunk in chunks) // <-- for each chunk

    foreach(var item in chunk) // <-- for each item in a chunk
    
        Console.WriteLine(item);
    

【讨论】:

【参考方案4】:

只进行了一些快速测试,但这似乎有效:

public static IEnumerable<IEnumerable<TValue>> Chunk<TValue>(this IEnumerable<TValue> values, Int32 chunkSize)

    var valuesList = values.ToList();
    var count = valuesList.Count();        
    for (var i = 0; i < (count / chunkSize) + (count % chunkSize == 0 ? 0 : 1); i++)
    
        yield return valuesList.Skip(i * chunkSize).Take(chunkSize);
    

【讨论】:

源序列的重复迭代可能有问题。 @spender 好点,我试图保持简短,所以忽略了 resharper 警告,但这只是额外的一行。 急切地完全枚举 IEnumerable 是有问题的,因为内存消耗。如果可以进行流式处理,则应使用它。考虑读取 2 Gb 文件或其中的几个。如果读者想一次只取几块,它应该能够期望使用这样一种方法,它需要一个IEnumerable,整个文件不会仅仅为了返回第一个块而被加载到内存中。跨度> 【参考方案5】:

如果您没有 .net 6,您可能会选择将 the Chunk method from it 修补到您的项目中。您可能需要进行的唯一调整是与 .net 源使用的异常帮助程序有关,因为您自己的项目可能没有 ThrowHelper

他们的代码:

ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);

可能更像:

throw new ArgumentNullException(nameof(source));

以下代码块已应用这些调整;您可以创建一个名为 Chunk.cs 的新文件并将以下代码放入其中:

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;

namespace System.Linq

    public static partial class Enumerable
    
        /// <summary>
        /// Split the elements of a sequence into chunks of size at most <paramref name="size"/>.
        /// </summary>
        /// <remarks>
        /// Every chunk except the last will be of size <paramref name="size"/>.
        /// The last chunk will contain the remaining elements and may be of a smaller size.
        /// </remarks>
        /// <param name="source">
        /// An <see cref="IEnumerableT"/> whose elements to chunk.
        /// </param>
        /// <param name="size">
        /// Maximum size of each chunk.
        /// </param>
        /// <typeparam name="TSource">
        /// The type of the elements of source.
        /// </typeparam>
        /// <returns>
        /// An <see cref="IEnumerableT"/> that contains the elements the input sequence split into chunks of size <paramref name="size"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="source"/> is null.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="size"/> is below 1.
        /// </exception>
        public static IEnumerable<TSource[]> Chunk<TSource>(this IEnumerable<TSource> source, int size)
        
            if (source == null)
            
                throw new ArgumentNullException(nameof(source));
            

            if (size < 1)
            
                throw new ArgumentOutOfRangeException(nameof(size));
            

            return ChunkIterator(source, size);
        

        private static IEnumerable<TSource[]> ChunkIterator<TSource>(IEnumerable<TSource> source, int size)
        
            using IEnumerator<TSource> e = source.GetEnumerator();
            while (e.MoveNext())
            
                TSource[] chunk = new TSource[size];
                chunk[0] = e.Current;

                int i = 1;
                for (; i < chunk.Length && e.MoveNext(); i++)
                
                    chunk[i] = e.Current;
                

                if (i == chunk.Length)
                
                    yield return chunk;
                
                else
                
                    Array.Resize(ref chunk, i);
                    yield return chunk;
                    yield break;
                
            
        
    

您应该确认将他们的 MIT 许可代码合并到您的项目中不会过度影响您自己的许可意图

【讨论】:

以上是关于我如何分块一个可枚举的?的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ4167永远的竹笋采摘 分块+树状数组

bzoj2453维护队列 (分块 + 二分)

如何从可枚举转换为特定模型

如何确定一种语言是递归的还是递归可枚举的?

如何创建无限可枚举的Times?

如何在 JavaScript 中使用可枚举