使用LINQ将集合拆分为`n`部分?

Sim*_*ver 121 .net c# linq data-structures

有没有一种很好的方法可以n使用LINQ 将集合拆分为多个部分?当然不一定均匀.

也就是说,我想将集合划分为子集合,每个子集合包含元素的子集,其中最后一个集合可以是不规则的.

Muh*_*han 126

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

  • 最好使用包含索引的Select重载. (8认同)
  • 你可以这样做:选择part.AsEnumerable()而不是select(IEnumerable <T>)部分.感觉更优雅. (3认同)
  • 在长列表上执行所有这些模数运算可能会有点贵. (2认同)
  • `.AsEnumerable()` 不是必需的,IGrouping&lt;T&gt; 已经是 IEnumerable&lt;T&gt;。 (2认同)

Jon*_*eet 58

编辑:好的,看起来我误解了这个问题.我把它读作"长度为n的片段"而不是"n片".卫生署!考虑删除答案......

(原始答案)

我不相信有一种内置的分区方法,虽然我打算在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你没有删除,即使它不是OP的答案,我想要完全相同的东西 - 长度为n :)的部分. (18认同)
  • 你*真的*不喜欢那些"array [count ++]",eh ;-p (3认同)
  • @Dejan:不,不.注意使用`yield return`.它需要一个*批处理*一次在内存中,但这就是全部. (2认同)

man*_*u08 37

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

  • 我对SQL风格的Linq有一种非理性的厌恶,所以这是我最喜欢的答案. (27认同)
  • 我有modulo的相同问题,所以我用`int columnLength =(int)Math.Ceiling((decimal)(list.Count())/ parts)计算列长度``然后用`.GroupBy(x)进行除法=> x.index/columnLength)`.一个缺点是Count()枚举列表. (3认同)

Mik*_*ike 24

好吧,我会戴上帽子.我的算法的优点:

  1. 没有昂贵的乘法,除法或模数运算符
  2. 所有操作都是O(1)(见下面的注释)
  3. 适用于IEnumerable <> source(不需要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();
}
Run Code Online (Sandbox Code Playgroud)

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

myEnum.Section(myEnum.Count() / number_of_sections + 1)
Run Code Online (Sandbox Code Playgroud)

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

  • 你的答案是对的,但问题是错的.您的答案为每个块提供了固定大小的未知数量的块.但OP需要一个Split功能,它可以提供固定数量的块,每个块具有任意大小(希望等于或接近相同的大小).也许更适合这里http://stackoverflow.com/questions/3773403/linq-partition-list-into-lists-of-8-members (4认同)
  • @ShadowChaser根据MSDN清除LinkedList是O(N)的复杂性,所以它会破坏我的O(1)的目标.当然,你可以说foreach是O(N)开头...... :) (3认同)

naw*_*fal 16

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

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

上述方法将一个IEnumerable<T>相等大小或接近相等大小的块分成N个.

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

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

Split除了速度较慢之外,该方法的问题在于,在分组将基于每个位置的N的倍数进行分组的意义上,它会对输出进行加扰,或者换句话说,您没有得到块.按原始顺序.

这里几乎每个答案要么不保留顺序,要么是分区而不是分裂,或者显然是错误的.试试这个更快,保留订单但是更详细:

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

这里Partition操作的等效方法


Elm*_*mer 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;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

请享用!

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