使用LINQ将集合拆分为n个零件吗?


122

有没有一种很好的方法将集合拆分为 n使用LINQ部分?当然不一定是均匀的。

也就是说,我想将集合分为子集合,每个子集合包含元素的子集,最后一个集合可能参差不齐。


1
重新标记:问题与asp.net没有关系。请适当标记您的问题。

如果不分配,您希望它们如何精确划分(当然允许结束)?
马克·格雷韦尔

1
谁链接到这个问题?约翰是你吗?:-)突然所有这些答案:-)
Simon_Weaver


@Simon_Weaver我尝试根据接受的答案来澄清您的要求。实际上,有很多方法可以“拆分”列表,包括将列表中的每个元素分解为元素,然后将其放入所谓的“并行”列表中。
jpaugh

Answers:


127

纯粹的linq和最简单的解决方案如下所示。

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}

3
您可以执行:选择part.AsEnumerable()而不是选择(IEnumerable <T>)part。感觉更优雅。
tuinstoel

2
在长列表上执行所有这些模运算可能会有点昂贵。
乔纳森·艾伦,2009年

8
最好使用包含索引的Select重载。
马克·格雷韦尔

1
我添加了使用选择重载和方法链接语法的响应
reustmd 2011年

1
.AsEnumerable()不需要,IGrouping <T>已经是IEnumerable <T>。
亚历克斯(Alex)

58

编辑:好的,看来我读错了问题。我将其读为“长度为n的片段”,而不是“ n个片段”。h!考虑删除答案...

(原始答案)

我不相信有一种内置的分区方法,尽管我打算在我的LINQ to Objects扩展集中写一个。Marc Gravell 在这里有一个实现,尽管我可能会对其进行修改以返回只读视图:

public static IEnumerable<IEnumerable<T>> Partition<T>
    (this IEnumerable<T> source, int size)
{
    T[] array = null;
    int count = 0;
    foreach (T item in source)
    {
        if (array == null)
        {
            array = new T[size];
        }
        array[count] = item;
        count++;
        if (count == size)
        {
            yield return new ReadOnlyCollection<T>(array);
            array = null;
            count = 0;
        }
    }
    if (array != null)
    {             
        Array.Resize(ref array, count);
        yield return new ReadOnlyCollection<T>(array);
    }
}

达恩(Darn)-击败我;-p
马克·

3
真的不喜欢那些“ array [count ++]”,;-p
Marc Gravell

18
感谢您不要删除,即使这不是OP的答案,我也希望完全一样-长度为n的片段:)。
Gishu 2012年

2
@Dejan:不,不是。注意的使用yield return。一次需要一批存储在内存中,仅此而已。
乔恩·斯基特

1
@Dejan:对-老实说,我不想猜测它如何与并行LINQ分区交互:)
Jon Skeet 2014年

39
static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
            return list.Select((item, index) => new {index, item})
                       .GroupBy(x => x.index % parts)
                       .Select(x => x.Select(y => y.item));
    }
}

28
我对SQL风格的Linq非常不喜欢,所以这是我最喜欢的答案。
piedar 2014年

1
@ manu08,我尝试过您的代码,我有一个列表var dept = {1,2,3,4,5}。分割后的结果是dept1 = {1,3,5}dept2 = { 2,4 }在哪里parts = 2。但结果我需要的是dept1 = {1,2,3}dept2 = {4,5}
KARTHIK Arthik

3
我对取模有同样的问题,所以我用计算了列的长度,int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);然后用进行了除法.GroupBy(x => x.index / columnLength)。缺点是Count()枚举列表。
goodeye

24

好吧,我将帽子戴上戒指。我的算法的优点:

  1. 无需昂贵的乘法,除法或模运算符
  2. 所有运算均为O(1)(请参阅下面的注释)
  3. 适用于IEnumerable <>源(不需要Count属性)
  4. 简单

代码:

public static IEnumerable<IEnumerable<T>>
  Section<T>(this IEnumerable<T> source, int length)
{
  if (length <= 0)
    throw new ArgumentOutOfRangeException("length");

  var section = new List<T>(length);

  foreach (var item in source)
  {
    section.Add(item);

    if (section.Count == length)
    {
      yield return section.AsReadOnly();
      section = new List<T>(length);
    }
  }

  if (section.Count > 0)
    yield return section.AsReadOnly();
}

正如下面的评论所指出的那样,这种方法实际上并没有解决原始问题,即要求固定数量的长度近似相等的部分。也就是说,您仍然可以通过以下方式使用我的方法来解决原始问题:

myEnum.Section(myEnum.Count() / number_of_sections + 1)

当以这种方式使用时,该方法不再是O(1),因为Count()操作为O(N)。


辉煌-这里最好的解决方案!一些优化:*清除链接列表,而不是为每个部分创建一个新列表。对链接列表的引用永远不会返回给调用者,因此它是完全安全的。*在到达第一项之前不要创建链接列表-这样,如果源为空,则不会进行分配
-ShadowChaser

3
根据MSDN,@ ShadowChaser清除了LinkedList的复杂度为O(N),因此它将破坏我的O(1)目标。当然,您可能会说foreach是O(N)以...开头:)
Mike

4
您的答案是正确的,但问题是错误的。您的答案给出了未知数量的块,每个块的大小固定。但是OP希望使用Split功能,该功能可以给出固定数量的块,每个块具有任意大小(希望大小相等或接近相等)。也许这里更适合stackoverflow.com/questions/3773403/...
nawfal

1
@Mike基准测试了吗?我希望您知道O(1)并不意味着更快,它仅意味着分区所需的时间不会扩展。我只是想知道,在所有现实情况下,盲目地坚持O(1)的速度比其他O(n)慢的理由是什么?我什至对它进行了疯狂的10 ^ 8强度测试,而我的速度似乎还更快。我希望你能知道有没有,甚至可以保存10 ^ 12项标准集合类型..
nawfal

1
@nawfal-感谢您的详细分析,它有助于我保持警惕。通常,链接列表以高效的最终插入而闻名,这就是我在这里选择它的原因。但是我只是对其进行了基准测试,确实List <>更快。我怀疑这是某种.NET实现细节,也许值得单独考虑一下StackOverflow问题。我已根据您的建议修改了答案以使用List <>。预分配列表容量可确保最终插入仍为O(1)并满足我的原始设计目标。我还切换到了.NET 4.5中的内置.AsReadOnly()。
Mike

16

这与已接受的答案相同,但表示形式更为简单:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

上面的方法将IEnumerable<T>n个大小相等或接近相等的块分割为N个。

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

上面的方法将 IEnumerable<T>分成所需固定大小的块,而块的总数并不重要-这不是问题所在。

的问题 Split方法,除了速度较慢之外,还在于它会扰乱输出,这意味着将根据每个位置的N的i倍进行分组,或者换句话说,您不会得到块按原始顺序。

几乎每个答案都不能保留顺序,或者是关于分区而不是拆分,或者显然是错误的。试试这个更快,保留顺序但稍微冗长的方法:

public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items, 
                                                   int numberOfChunks)
{
    if (numberOfChunks <= 0 || numberOfChunks > items.Count)
        throw new ArgumentOutOfRangeException("numberOfChunks");

    int sizePerPacket = items.Count / numberOfChunks;
    int extra = items.Count % numberOfChunks;

    for (int i = 0; i < numberOfChunks - extra; i++)
        yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);

    int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
    int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
    for (int i = 0; i < extra; i++)
        yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}

此处的等效Partition操作方法


6

我一直在使用我以前发布的分区功能。唯一的坏处是没有完全流式传输。如果您只处理序列中的几个元素,那么这不是问题。当我开始按顺序使用100.000+个元素时,我需要一个新的解决方案。

以下解决方案要复杂得多(并且代码更多!),但是它非常有效。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace LuvDaSun.Linq
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
        {
            /*
            return enumerable
                .Select((item, index) => new { Item = item, Index = index, })
                .GroupBy(item => item.Index / partitionSize)
                .Select(group => group.Select(item => item.Item)                )
                ;
            */

            return new PartitioningEnumerable<T>(enumerable, partitionSize);
        }

    }


    class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        IEnumerable<T> _enumerable;
        int _partitionSize;
        public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
        {
            _enumerable = enumerable;
            _partitionSize = partitionSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
        }

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


    class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
            _enumerator.Dispose();
        }

        IEnumerable<T> _current;
        public IEnumerable<T> Current
        {
            get { return _current; }
        }
        object IEnumerator.Current
        {
            get { return _current; }
        }

        public void Reset()
        {
            _current = null;
            _enumerator.Reset();
        }

        public bool MoveNext()
        {
            bool result;

            if (_enumerator.MoveNext())
            {
                _current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
                result = true;
            }
            else
            {
                _current = null;
                result = false;
            }

            return result;
        }

    }



    class PartitionEnumerable<T> : IEnumerable<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new PartitionEnumerator<T>(_enumerator, _partitionSize);
        }

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


    class PartitionEnumerator<T> : IEnumerator<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        int _count;
        public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
        }

        public T Current
        {
            get { return _enumerator.Current; }
        }
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }
        public void Reset()
        {
            if (_count > 0) throw new InvalidOperationException();
        }

        public bool MoveNext()
        {
            bool result;

            if (_count < _partitionSize)
            {
                if (_count > 0)
                {
                    result = _enumerator.MoveNext();
                }
                else
                {
                    result = true;
                }
                _count++;
            }
            else
            {
                result = false;
            }

            return result;
        }

    }
}

请享用!


此版本违反了IEnumerator的约定。调用Reset时抛出InvalidOperationException是无效的-我相信许多LINQ扩展方法都依赖于此行为。
ShadowChaser

1
@ShadowChaser我认为Reset()应该抛出NotSupportedException,一切都会好起来的。从MSDN文档中:“提供Reset方法是为了实现COM的互操作性。不一定需要实现它;相反,实现者可以简单地抛出NotSupportedException。”
13年

@toong哇,你是对的。一直以来我都不确定我是怎么想念它的。
ShadowChaser 2013年

越野车!我记不清了,但是(据我所记得)它执行了不必要的步骤,并且可能导致丑陋的副作用(例如使用datareader)。最好的解决办法是在这里(叶普斯蒂格·尼尔森):stackoverflow.com/questions/13709626/...
SalientBrain

4

有趣的线程。要获得分割/分区的流式版本,可以使用枚举器,并使用扩展方法从枚举器产生序列。使用yield将命令性代码转换为功能代码确实是一种非常强大的技术。

首先是一个枚举器扩展,它将元素的数量转换为惰性序列:

public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
    while (count > 0)
    {
        yield return enumerator.Current;
        if (--count > 0 && !enumerator.MoveNext()) yield break;
    }
}

然后是对序列进行分区的可枚举扩展:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
    var enumerator = seq.GetEnumerator();

    while (enumerator.MoveNext())
    {
        yield return enumerator.TakeFromCurrent(partitionSize);
    }
}

最终结果是高效,流式处理和惰性实现,它依赖于非常简单的代码。

请享用!


我最初编写了相同的东西,但是当在嵌套的IEnumerable <T>实例之一上调用Reset时,模式会中断。
ShadowChaser

1
如果仅枚举分区而不是内部枚举,这仍然有效吗?由于内部枚举器被延迟,因此直到被枚举为止,内部的任何代码(从当前获取)都不会执行,因此movenext()将仅由外部分区函数调用,对吗?如果我的假设是正确的,那么这可能会在原始可枚举中产生n个具有n个元素的分区,而内部可枚举将产生意想不到的结果
Brad

@Brad会“失败”如您所愿,类似于一些在此线程的问题stackoverflow.com/questions/419019/...(特别stackoverflow.com/a/20953521/1037948
drzaus

4

我用这个:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize)
{
    return instance
        .Select((value, index) => new { Index = index, Value = value })
        .GroupBy(i => i.Index / partitionSize)
        .Select(i => i.Select(i2 => i2.Value));
}

请解释原因。我一直在使用此功能,没有任何麻烦!
Elmer

再次阅读问题,看看是否有n个(几乎)等长的部分与您的函数
相符

@Elmer您的回答是正确的,但问题是错误的。您的答案给出了未知数量的块,每个块的大小都是固定的(正好是“ Partition”,即您为其指定的名称)。但是OP希望使用Split功能,该功能可以给出固定数量的块,每个块具有任意大小(希望大小相等或接近相等)。也许这里更适合stackoverflow.com/questions/3773403/...
nawfal

我认为您可以将i.Index / partitionSize更改为i.Index%partitionSize并获取请求的结果。我也更喜欢这个,而不是公认的答案,因为它更紧凑,更易读。
杰克·德鲁

2

这样可以提高内存效率,并尽可能延缓执行(每批),并在线性时间O(n)中运行

    public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize)
    {
        List<T> batch = new List<T>(batchSize);
        foreach (var item in items)
        {
            batch.Add(item);

            if (batch.Count >= batchSize)
            {
                yield return batch;
                batch = new List<T>();
            }
        }

        if (batch.Count != 0)
        {
            //can't be batch size or would've yielded above
            batch.TrimExcess();
            yield return batch;
        }
    }

2

这个问题(及其表亲)有很多很好的答案。我自己需要这个,并且创建了一个解决方案,该解决方案旨在在将源集合视为列表的情况下高效且容错。它不使用任何延迟迭代,因此它可能不适合可能施加内存压力的未知大小的集合。

static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize)
{
    IList<T[]> result = null;
    if (source != null && batchsize > 0)
    {
        var list = source as List<T> ?? source.ToList();
        if (list.Count > 0)
        {
            result = new List<T[]>();
            for (var index = 0; index < list.Count; index += batchsize)
            {
                var rangesize = Math.Min(batchsize, list.Count - index);
                result.Add(list.GetRange(index, rangesize).ToArray());
            }
        }
    }
    return result ?? Enumerable.Empty<T[]>().ToList();
}

static public void TestGetChunks()
{
    var ids = Enumerable.Range(1, 163).Select(i => i.ToString());
    foreach (var chunk in ids.GetChunks(20))
    {
        Console.WriteLine("[{0}]", String.Join(",", chunk));
    }
}

我在使用GetRange和Math.Min的一系列问题中看到了一些答案。但是我相信总体而言,从错误检查和效率方面来说,这是一个更完整的解决方案。


1
   protected List<List<int>> MySplit(int MaxNumber, int Divider)
        {
            List<List<int>> lst = new List<List<int>>();
            int ListCount = 0;
            int d = MaxNumber / Divider;
            lst.Add(new List<int>());
            for (int i = 1; i <= MaxNumber; i++)
            {
                lst[ListCount].Add(i);
                if (i != 0 && i % d == 0)
                {
                    ListCount++;
                    d += MaxNumber / Divider;
                    lst.Add(new List<int>());
                }
            }
            return lst;
        }

1

好答案,对于我的情况,我测试了可接受的答案,看来它并不能保持秩序。纳瓦尔(Nawfal)也有很好的答案,可以保持秩序。但是在我的场景中,我想以归一化的方式拆分剩余部分,我看到的所有答案都将剩余部分或开始或结束时的结果分散了。

我的回答还使余数以更规范的方式扩展。

 static class Program
{          
    static void Main(string[] args)
    {
        var input = new List<String>();
        for (int k = 0; k < 18; ++k)
        {
            input.Add(k.ToString());
        }
        var result = splitListIntoSmallerLists(input, 15);            
        int i = 0;
        foreach(var resul in result){
            Console.WriteLine("------Segment:" + i.ToString() + "--------");
            foreach(var res in resul){
                Console.WriteLine(res);
            }
            i++;
        }
        Console.ReadLine();
    }

    private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists)
    {
        if (i_numberOfSmallerLists <= 0)
            throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists");

        int normalizedSpreadRemainderCounter = 0;
        int normalizedSpreadNumber = 0;
        //e.g 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2          
        int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists;                        
        int remainder = i_bigList.Count % i_numberOfSmallerLists;
        int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder;
        //In case remainder > 0 we want to spread the remainder equally between the others         
        if (remainder > 0)
        {
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder);    
            }
            else
            {
                normalizedSpreadNumber = 1;
            }   
        }
        List<List<T>> retVal = new List<List<T>>(outputSize);
        int inputIndex = 0;            
        for (int i = 0; i < outputSize; ++i)
        {
            retVal.Add(new List<T>());
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList));
                inputIndex += minimumNumberOfPartsInEachSmallerList;
            }
            //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber.
            if (remainder > 0)
            {
                if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1)
                {
                    retVal[i].Add(i_bigList[inputIndex]);
                    remainder--;
                    inputIndex++;
                    normalizedSpreadRemainderCounter=0;
                }
                else
                {
                    normalizedSpreadRemainderCounter++;
                }
            }
        }
        return retVal;
    }      

}

0

如果这些部分的顺序不是很重要,则可以尝试以下操作:

int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = 3;

var result =
   array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value);

// or
var result2 =
   from i in array.Select((value, index) => new { Value = value, Index = index })
   group i.Value by i.Index % n into g
   select g;

但是由于某种原因,这些不能转换为IEnumerable <IEnumerable <int >>。


可以办到。除了直接转换外,只需使函数通用,然后为您的int数组调用它即可
nawfal 2012年

0

这是我的代码,很简短。

 <Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T))
     Dim result As New List(Of List(Of T))
     For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1
         result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size)))))
     Next
     Return result
 End Function

0

这是我的方式,列出项目并逐列中断

  int repat_count=4;

  arrItems.ForEach((x, i) => {
    if (i % repat_count == 0) 
        row = tbo.NewElement(el_tr, cls_min_height);
    var td = row.NewElement(el_td);
    td.innerHTML = x.Name;
  });

0

我一直在寻找类似于带字符串的拆分方式,因此整个列表是根据某些规则拆分的,不仅是第一部分,这是我的解决方案

List<int> sequence = new List<int>();
for (int i = 0; i < 2000; i++)
{
     sequence.Add(i);
}
int splitIndex = 900;
List<List<int>> splitted = new List<List<int>>();
while (sequence.Count != 0)
{
    splitted.Add(sequence.Take(splitIndex).ToList() );
    sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count));
}

下次尝试:var nrs = Enumerable.Range(1,2000).ToList();
MBoros

0

这是对项数而不是零件数的一些调整:

public static class MiscExctensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems)
    {
        return (
            list
            .Select((o, n) => new { o, n })
            .GroupBy(g => (int)(g.n / nbItems))
            .Select(g => g.Select(x => x.o))
        );
    }
}

-1
int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 };

int itemIndex = 0;
int groupSize = 2;
int nextGroup = groupSize;

var seqItems = from aItem in items
               group aItem by 
                            (itemIndex++ < nextGroup) 
                            ? 
                            nextGroup / groupSize
                            :
                            (nextGroup += groupSize) / groupSize
                            into itemGroup
               select itemGroup.AsEnumerable();

-1

刚好碰到这个线程,这里的大多数解决方案都涉及向集合中添加项目,在返回每个页面之前有效地实现每个页面。不好的原因有两个:首先,如果您的页面很大,则需要占用大量内存来填充页面;其次,有一些迭代器会在前进到下一个记录时使先前的记录无效(例如,如果将DataReader封装在枚举器方法中) 。

此解决方案使用两个嵌套的枚举器方法,以避免需要将项目缓存到临时集合中。由于外部迭代器和内部迭代器遍历相同的枚举,因此它们必须共享相同的枚举器,因此,在处理完当前页面之前,请不要前进外部迭代器,这一点很重要。也就是说,如果您决定不完全迭代当前页面,那么当您移至下一页时,此解决方案将自动迭代到页面边界。

using System.Collections.Generic;

public static class EnumerableExtensions
{
    /// <summary>
    /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
    /// </summary>
    /// <typeparam name="T">The element type</typeparam>
    /// <param name="enumerable">The source enumerable</param>
    /// <param name="pageSize">The number of elements to return in each page</param>
    /// <returns></returns>
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
    {
        var enumerator = enumerable.GetEnumerator();

        while (enumerator.MoveNext())
        {
            var indexWithinPage = new IntByRef { Value = 0 };

            yield return SubPartition(enumerator, pageSize, indexWithinPage);

            // Continue iterating through any remaining items in the page, to align with the start of the next page
            for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
            }
        }
    }

    private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
    {
        for (; index.Value < pageSize; index.Value++)
        {
            yield return enumerator.Current;

            if (!enumerator.MoveNext())
            {
                yield break;
            }
        }
    }

    private class IntByRef
    {
        public int Value { get; set; }
    }
}

这根本不起作用!最好的可能是在这里stackoverflow.com/questions/13709626/…!看评论。
SalientBrain 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.