使用LINQ将列表拆分为子列表

Fel*_*ima 361 c# linq data-structures

有没有什么方法可以将项目索引作为每个拆分的分隔符List<SomeObject>分成几个单独的列表SomeObject

让我举例说明:

我有一个List<SomeObject>,我需要一个List<List<SomeObject>>List<SomeObject>[],所以这些结果列表中的每一个将包含一组3个原始列表项(顺序).

例如.:

  • 原始清单: [a, g, e, w, p, s, q, f, x, y, i, m, c]

  • 结果列表: [a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]

我还需要将结果列表大小作为此函数的参数.

Jar*_*Par 357

请尝试以下代码.

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();
}
Run Code Online (Sandbox Code Playgroud)

我们的想法是首先按索引对元素进行分组.除以三有其分组为3.组,那么每组转换到一个列表的效果和IEnumerableListListList小号

  • GroupBy进行隐式排序.这可能会扼杀性能.我们需要的是SelectMany的某种反转. (20认同)
  • 以无限的IEnumerable为例.`GroupBy(x => f(x)).First()`永远不会产生一个组.OP询问了列表,但是如果我们写入使用IEnumerable,只进行一次迭代,我们就会获得性能优势. (9认同)
  • @Nick Order不会保留你的方式.知道它仍然是一件好事,但你要将它们分组为(0,3,6,9,...),(1,4,7,10,......),(2,5,8) ,11,......).如果订单没关系那么它很好但在这种情况下听起来很重要. (8认同)
  • @Justice,GroupBy可能是通过散列实现的.你怎么知道GroupBy的实施"可以扼杀性能"? (5认同)
  • GroupBy在枚举所有元素之前不会返回任何内容.这就是为什么它很慢.OP想要的列表是连续的,因此更好的方法可以在枚举原始列表的任何更多之前产生第一个子列表`[a,g,e]`. (5认同)
  • 值得注意的是`.GroupBy(x => x.Index%3)`将整个集合平均分成3个部分,所以如果你有30个项目,你将得到3个10个列表.当前的例子给你10个列表如果你有30,那就是3. (3认同)
  • @Justice,为IEnumerable建立一个内置的分区系统会很不错. (2认同)
  • 将`IList &lt;IList &lt;T &gt;&gt; Split &lt;T&gt;(IList &lt;T&gt; source)更改为`IList &lt;List &lt;T &gt;&gt; Split &lt;T&gt;(IList &lt;T&gt; source)` (2认同)

Cas*_*eyB 310

这个问题有点陈旧,但我刚刚写了这篇文章,我认为它比其他提议的解决方案更优雅:

/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个,但时间效率是"O(n²)".您可以遍历列表并获得"O(n)"时间. (50认同)
  • 喜欢这个解决方案 我建议添加这个健全性检查以防止无限循环:`if(chunksize <= 0)抛出新的ArgumentException("块大小必须大于零.","chunksize"); (13认同)
  • @vivekmaharajh`source`每次都被包裹的`IEnumerable`取代.因此,从"来源"中获取元素会经历"跳过"的各个层次 (13认同)
  • 我喜欢这个,但它不是超级高效的 (10认同)
  • @hIpPy,怎么样n ^ 2?看起来对我线性 (8认同)
  • @vivekmaharajh:为什么你认为列表或数组使它成为'0(n)`,它们没有被优化,'Skip`和`Take`总是枚举序列,直到那个点为'O(n ^ 2)`复杂.在一个非常大的列表上,Skip/Take apporaches是无用的.我使用整数除法的'GroupBy`或(更高效)[MoreLINQ的'批量'](https://code.google.com/p/morelinq/source/browse/MoreLinq/Batch.cs?r=f85495b139a19bce7df2be98ad88754ba8932a28 ). (5认同)
  • @hIpPy这确实是O(n ^ 2)延迟执行`IEnumerable`s.但是如果你有一个普通的`array []`或`List`,它将是'O(n)`.如果你喜欢这个解决方案而且你有一个`IEnumerable`,你可以在将它传递给`Chunk`之前使用`.ToArray()`,然后得到`O(n)`.当然,缺点是额外的内存和潜在的额外性能(如果你有可能不会枚举所有块的调用者,你将不必要地枚举所有的`source`).由于这些缺点,像@hlpPy建议的那样自己手动进行迭代可能是生产代码的更好选择. (3认同)
  • [.NET 6 / C#10](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.chunk?view=net-6.0) 中现在提供了类似的方法 (3认同)
  • 对于块大小为 3 的 13 个元素序列,该算法将访问元素 91 次。对于小序列来说不是问题,但对于大序列来说效率很低。 (2认同)

Sam*_*ron 94

一般来说,CaseyB建议的方法运行正常,事实上如果你传入List<T>它很难将它弄错,也许我会把它改为:

public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
   var pos = 0; 
   while (source.Skip(pos).Any())
   {
      yield return source.Skip(pos).Take(chunksize);
      pos += chunksize;
   }
}
Run Code Online (Sandbox Code Playgroud)

这将避免大规模的呼叫链.尽管如此,这种方法有一个普遍的缺陷.它实现了每个块的两个枚举,以突出显示尝试运行的问题:

foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
   Console.WriteLine(item);
}
// wait forever 
Run Code Online (Sandbox Code Playgroud)

为了克服这个问题,我们可以尝试使用Cameron的方法,该方法通过上述测试以飞行颜色进行,因为它只进行一次枚举.

麻烦的是它有一个不同的缺陷,它实现了每个块中的每个项目,这种方法的麻烦在于你在内存上运行得很高.

为了说明尝试运行:

foreach (var item in Enumerable.Range(1, int.MaxValue)
               .Select(x => x + new string('x', 100000))
               .Clump(10000).Skip(100).First())
{
   Console.Write('.');
}
// OutOfMemoryException
Run Code Online (Sandbox Code Playgroud)

最后,任何实现都应该能够处理块的乱序迭代,例如:

Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]
Run Code Online (Sandbox Code Playgroud)

许多高度优化的解决方案,例如我对此答案的第一次修订失败了 在casperOne的优化答案中可以看到同样的问题.

要解决所有这些问题,您可以使用以下内容:

namespace ChunkedEnumerator
{
    public static class Extensions 
    {
        class ChunkedEnumerable<T> : IEnumerable<T>
        {
            class ChildEnumerator : IEnumerator<T>
            {
                ChunkedEnumerable<T> parent;
                int position;
                bool done = false;
                T current;


                public ChildEnumerator(ChunkedEnumerable<T> parent)
                {
                    this.parent = parent;
                    position = -1;
                    parent.wrapper.AddRef();
                }

                public T Current
                {
                    get
                    {
                        if (position == -1 || done)
                        {
                            throw new InvalidOperationException();
                        }
                        return current;

                    }
                }

                public void Dispose()
                {
                    if (!done)
                    {
                        done = true;
                        parent.wrapper.RemoveRef();
                    }
                }

                object System.Collections.IEnumerator.Current
                {
                    get { return Current; }
                }

                public bool MoveNext()
                {
                    position++;

                    if (position + 1 > parent.chunkSize)
                    {
                        done = true;
                    }

                    if (!done)
                    {
                        done = !parent.wrapper.Get(position + parent.start, out current);
                    }

                    return !done;

                }

                public void Reset()
                {
                    // per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
                    throw new NotSupportedException();
                }
            }

            EnumeratorWrapper<T> wrapper;
            int chunkSize;
            int start;

            public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
            {
                this.wrapper = wrapper;
                this.chunkSize = chunkSize;
                this.start = start;
            }

            public IEnumerator<T> GetEnumerator()
            {
                return new ChildEnumerator(this);
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

        }

        class EnumeratorWrapper<T>
        {
            public EnumeratorWrapper (IEnumerable<T> source)
            {
                SourceEumerable = source;
            }
            IEnumerable<T> SourceEumerable {get; set;}

            Enumeration currentEnumeration;

            class Enumeration
            {
                public IEnumerator<T> Source { get; set; }
                public int Position { get; set; }
                public bool AtEnd { get; set; }
            }

            public bool Get(int pos, out T item) 
            {

                if (currentEnumeration != null && currentEnumeration.Position > pos)
                {
                    currentEnumeration.Source.Dispose();
                    currentEnumeration = null;
                }

                if (currentEnumeration == null)
                {
                    currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
                }

                item = default(T);
                if (currentEnumeration.AtEnd)
                {
                    return false;
                }

                while(currentEnumeration.Position < pos) 
                {
                    currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
                    currentEnumeration.Position++;

                    if (currentEnumeration.AtEnd) 
                    {
                        return false;
                    }

                }

                item = currentEnumeration.Source.Current;

                return true;
            }

            int refs = 0;

            // needed for dispose semantics 
            public void AddRef()
            {
                refs++;
            }

            public void RemoveRef()
            {
                refs--;
                if (refs == 0 && currentEnumeration != null)
                {
                    var copy = currentEnumeration;
                    currentEnumeration = null;
                    copy.Source.Dispose();
                }
            }
        }

        public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
        {
            if (chunksize < 1) throw new InvalidOperationException();

            var wrapper =  new EnumeratorWrapper<T>(source);

            int currentPos = 0;
            T ignore;
            try
            {
                wrapper.AddRef();
                while (wrapper.Get(currentPos, out ignore))
                {
                    yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
                    currentPos += chunksize;
                }
            }
            finally
            {
                wrapper.RemoveRef();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int i = 10;
            foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
            {
                foreach (var n in group)
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
                if (i-- == 0) break;
            }


            var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();

            foreach (var idx in new [] {3,2,1})
            {
                Console.Write("idx " + idx + " ");
                foreach (var n in stuffs[idx])
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
            }

            /*

10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
             */

            Console.ReadKey();


        }

    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以为块的无序迭代引入一轮优化,这超出了范围.

至于你应该选择哪种方法?这完全取决于您试图解决的问题.如果你不关心第一个缺陷,简单的答案是非常吸引人的.

请注意,与大多数方法一样,这对于多线程是不安全的,如果您希望使线程安全,您可能需要修改,这会很奇怪EnumeratorWrapper.


Lib*_*tad 66

更新.NET 6.0

\n

.NET 6.0在 System.Linq 命名空间中添加了一个新的本机Chunk方法:

\n
public static System.Collections.Generic.IEnumerable<TSource[]> Chunk<TSource> (\n   this System.Collections.Generic.IEnumerable<TSource> source, int size);\n
Run Code Online (Sandbox Code Playgroud)\n

使用这种新方法,除最后一个块之外的每个块的大小都将为size。最后一个块将包含剩余的元素,并且可能具有较小的大小。

\n

这是一个例子:

\n
var list = Enumerable.Range(1, 100);\nvar chunkSize = 10;\n\nforeach(var chunk in list.Chunk(chunkSize)) //Returns a chunk with the correct size. \n{\n    Parallel.ForEach(chunk, (item) =>\n    {\n        //Do something Parallel here. \n        Console.WriteLine(item);\n    });\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您\xe2\x80\x99 可能会想,为什么不使用 Skip 和 Take 呢?确实如此,我认为这更加简洁,并且使内容更具可读性。

\n


cas*_*One 65

可以使用许多使用Take和的查询Skip,但是我认为这会在原始列表上添加太多迭代.

相反,我认为你应该创建一个自己的迭代器,如下所示:

public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
  IEnumerable<T> enumerable, int groupSize)
{
   // The list to return.
   List<T> list = new List<T>(groupSize);

   // Cycle through all of the items.
   foreach (T item in enumerable)
   {
     // Add the item.
     list.Add(item);

     // If the list has the number of elements, return that.
     if (list.Count == groupSize)
     {
       // Return the list.
       yield return list;

       // Set the list to a new list.
       list = new List<T>(groupSize);
     }
   }

   // Return the remainder if there is any,
   if (list.Count != 0)
   {
     // Return the list.
     yield return list;
   }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以调用它并启用LINQ,以便对结果序列执行其他操作.


根据Sam的回答,我觉得有一种更简单的方法可以做到这一点:

  • 再次遍历列表(我最初没有这样做)
  • 在释放块之前实现组中的项目(对于大块项目,会出现内存问题)
  • Sam发布的所有代码

也就是说,这是另一个传递,我已经编写了一个扩展方法来IEnumerable<T>调用Chunk:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
    int chunkSize)
{
    // Validate parameters.
    if (source == null) throw new ArgumentNullException("source");
    if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
        "The chunkSize parameter must be a positive value.");

    // Call the internal implementation.
    return source.ChunkInternal(chunkSize);
}
Run Code Online (Sandbox Code Playgroud)

没有什么令人惊讶的,只是基本的错误检查.

继续ChunkInternal:

private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
    this IEnumerable<T> source, int chunkSize)
{
    // Validate parameters.
    Debug.Assert(source != null);
    Debug.Assert(chunkSize > 0);

    // Get the enumerator.  Dispose of when done.
    using (IEnumerator<T> enumerator = source.GetEnumerator())
    do
    {
        // Move to the next element.  If there's nothing left
        // then get out.
        if (!enumerator.MoveNext()) yield break;

        // Return the chunked sequence.
        yield return ChunkSequence(enumerator, chunkSize);
    } while (true);
}
Run Code Online (Sandbox Code Playgroud)

基本上,它获取IEnumerator<T>并手动遍历每个项目.它会检查当前是否有任何项目被枚举.在枚举每个块之后,如果没有剩余任何项目,它就会爆发.

一旦检测到序列中有项目,它就会将内部IEnumerable<T>实现的职责委托给ChunkSequence:

private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator, 
    int chunkSize)
{
    // Validate parameters.
    Debug.Assert(enumerator != null);
    Debug.Assert(chunkSize > 0);

    // The count.
    int count = 0;

    // There is at least one item.  Yield and then continue.
    do
    {
        // Yield the item.
        yield return enumerator.Current;
    } while (++count < chunkSize && enumerator.MoveNext());
}
Run Code Online (Sandbox Code Playgroud)

由于MoveNext已经调用了IEnumerator<T>传递给ChunkSequence它,它产生返回的项目Current然后递增计数,确保永远不会返回多于chunkSize项目并在每次迭代后移动到序列中的下一个项目(但如果数量为产生的物品超过了块大小).

如果没有剩下的项目,那么该InternalChunk方法将在外部循环中进行另一次传递,但是当MoveNext第二次调用时,它仍将返回false,如文档所示(强调我的):

如果MoveNext传递集合的末尾,则枚举数位于集合中的最后一个元素之后,MoveNext返回false.当枚举器处于此位置时,后续对MoveNext的调用也会返回false,直到调用Reset.

此时,循环将中断,序列序列将终止.

这是一个简单的测试:

static void Main()
{
    string s = "agewpsqfxyimc";

    int count = 0;

    // Group by three.
    foreach (IEnumerable<char> g in s.Chunk(3))
    {
        // Print out the group.
        Console.Write("Group: {0} - ", ++count);

        // Print the items.
        foreach (char c in g)
        {
            // Print the item.
            Console.Write(c + ", ");
        }

        // Finish the line.
        Console.WriteLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Run Code Online (Sandbox Code Playgroud)

一个重要的注意,这会不会,如果你不漏整个子序列或亲本序列中任何一点突破的工作.这是一个重要的警告,但如果您的用例是您将使用序列序列的每个元素,那么这将适合您.

另外,如果你玩这个命令,它会做一些奇怪的事情,就像Sam在某一点上做的那样.


3dG*_*ber 46

好的,这是我的看法:

  • 完全懒惰:适用于无限的枚举
  • 没有中间复制/缓冲
  • O(n)执行时间
  • 当内部序列仅部分消耗时也起作用

public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable,
                                                    int chunkSize)
{
    if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");

    using (var e = enumerable.GetEnumerator())
    while (e.MoveNext())
    {
        var remaining = chunkSize;    // elements remaining in the current chunk
        var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());

        yield return e.GetChunk(innerMoveNext);
        while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
    }
}

private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e,
                                          Func<bool> innerMoveNext)
{
    do yield return e.Current;
    while (innerMoveNext());
}
Run Code Online (Sandbox Code Playgroud)

示例用法

var src = new [] {1, 2, 3, 4, 5, 6}; 

var c3 = src.Chunks(3);      // {{1, 2, 3}, {4, 5, 6}}; 
var c4 = src.Chunks(4);      // {{1, 2, 3, 4}, {5, 6}}; 

var sum   = c3.Select(c => c.Sum());    // {6, 15}
var count = c3.Count();                 // 2
var take2 = c3.Select(c => c.Take(2));  // {{1, 2}, {4, 5}}
Run Code Online (Sandbox Code Playgroud)

说明

代码通过嵌套两个yield基于迭代器来工作.

外部迭代器必须跟踪内部(块)迭代器有效消耗的元素数量.这是通过闭合在完成remaininginnerMoveNext().在外迭代器产生下一个块之前,丢弃未使用的块元素.这是必要的,因为否则当内部枚举没有(完全)消耗时(例如c3.Count()将返回6),你会得到不一致的结果.

注意: 答案已经更新,以解决@aolszowka指出的缺点.

  • 非常好.我的"正确"解决方案比这更复杂.这是#1答案恕我直言. (2认同)

xto*_*ofs 16

完全懒惰,不计数或复制:

public static class EnumerableExtensions
{

  public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
  {
     if (len == 0)
        throw new ArgumentNullException();

     var enumer = source.GetEnumerator();
     while (enumer.MoveNext())
     {
        yield return Take(enumer.Current, enumer, len);
     }
  }

  private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
  {
     while (true)
     {
        yield return head;
        if (--len == 0)
           break;
        if (tail.MoveNext())
           head = tail.Current;
        else
           break;
     }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为这不会失败.但它肯定会有一些奇怪的行为.如果您有100个项目,并且您分成10个批次,并且您枚举了所有批次而未枚举这些批次中的任何项目,那么您最终将获得100个批次的1个批次. (3认同)

Mar*_*and 12

我认为以下建议将是最快的.我正在牺牲源Enumerable的懒惰,因为它能够使用Array.Copy并提前知道每个子列表的长度.

public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> items, int size)
{
    T[] array = items as T[] ?? items.ToArray();
    for (int i = 0; i < array.Length; i+=size)
    {
        T[] chunk = new T[Math.Min(size, array.Length - i)];
        Array.Copy(array, i, chunk, 0, chunk.Length);
        yield return chunk;
    }
}
Run Code Online (Sandbox Code Playgroud)


Col*_*nic 9

我们可以改进@JaredPar的解决方案来进行真正的懒惰评估.我们使用一种GroupAdjacentBy方法,产生具有相同键的连续元素组:

sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))
Run Code Online (Sandbox Code Playgroud)

由于这些组是逐个产生的,因此该解决方案可以有效地使用长序列或无限序列.


Kev*_*oid 9

对于对打包/维护解决方案感兴趣的任何人,MoreLINQ库提供了Batch与您请求的行为相匹配的扩展方法:

IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);
Run Code Online (Sandbox Code Playgroud)

Batch实现类似于Cameron MacFarland 的 answer,添加了用于在返回之前转换块/批处理的重载,并且性能非常好。

  • 这应该是公认的答案。应该使用morelinq,而不是重新发明轮子 (2认同)
  • 的确。检查了 github 上的源代码,它优于此页面上的任何内容。包括我的答案:)我最初确实检查了moreLinq,但我正在寻找名称中带有“Chunk”的东西。 (2认同)

dah*_*byk 8

System.Interactive提供Buffer()此目的.一些快速测试显示性能类似于Sam的解决方案.


Cam*_*and 8

几年前我写了一个Clump扩展方法.效果很好,是这里最快的实现.:P

/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (size < 1)
        throw new ArgumentOutOfRangeException("size", "size must be greater than 0");

    return ClumpIterator<T>(source, size);
}

private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
    Debug.Assert(source != null, "source is null.");

    T[] items = new T[size];
    int count = 0;
    foreach (var item in source)
    {
        items[count] = item;
        count++;

        if (count == size)
        {
            yield return items;
            items = new T[size];
            count = 0;
        }
    }
    if (count > 0)
    {
        if (count == size)
            yield return items;
        else
        {
            T[] tempItems = new T[count];
            Array.Copy(items, tempItems, count);
            yield return tempItems;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Amy*_*y B 6

这是我几个月前写的一个列表拆分程序:

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}
Run Code Online (Sandbox Code Playgroud)


mwj*_*son 5

我们发现 David B 的解决方案效果最好。但我们将其调整为更通用的解决方案:

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();
Run Code Online (Sandbox Code Playgroud)

  • 这很好,但与最初的提问者所要求的完全不同。 (3认同)

Rom*_*kar 5

这个如何?

var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3

var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
                    .Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
                    .ToList();
Run Code Online (Sandbox Code Playgroud)

据我所知,GetRange()在所取项目数方面是线性的。所以这应该表现良好。


aol*_*wka 5

这是一个老问题,但这是我最终得到的; 它只列举一次枚举,但会为每个分区创建列表.当ToArray()某些实现调用时,它不会受到意外行为的影响:

    public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (chunkSize < 1)
        {
            throw new ArgumentException("Invalid chunkSize: " + chunkSize);
        }

        using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
        {
            IList<T> currentChunk = new List<T>();
            while (sourceEnumerator.MoveNext())
            {
                currentChunk.Add(sourceEnumerator.Current);
                if (currentChunk.Count == chunkSize)
                {
                    yield return currentChunk;
                    currentChunk = new List<T>();
                }
            }

            if (currentChunk.Any())
            {
                yield return currentChunk;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)


Rob*_*Kee 5

旧代码,但这是我一直在使用的:

    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        var toReturn = new List<T>(max);
        foreach (var item in source)
        {
            toReturn.Add(item);
            if (toReturn.Count == max)
            {
                yield return toReturn;
                toReturn = new List<T>(max);
            }
        }
        if (toReturn.Any())
        {
            yield return toReturn;
        }
    }
Run Code Online (Sandbox Code Playgroud)


erl*_*ndo 5

我发现这个小片段做得很好.

public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
    var offset = 0;

    while (offset < source.Count)
    {
        yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
        offset += chunkSize;
    }
}
Run Code Online (Sandbox Code Playgroud)