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

时间:2009-01-13 07:14:35

标签: c# .net linq data-structures

使用LINQ将集合拆分为n部分有什么好方法吗? 当然不一定均匀。

也就是说,我想将集合划分为子集合,每个子集合包含元素的子集,其中最后一个集合可以是粗糙的。​​

20 个答案:

答案 0 :(得分:125)

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

答案 1 :(得分:57)

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

(原始答案)

我不相信有一种内置的分区方式,虽然我打算在LINQ to Objects的一组添加中编写一个。 Marc Gravell有implementation here虽然我可能会修改它以返回只读视图:

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

答案 2 :(得分:33)

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

答案 3 :(得分:23)

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

  1. 没有昂贵的乘法,除法或模数运算符
  2. 所有操作均为O(1)(参见下面的注释)
  3. 适用于IEnumerable&lt;&gt;来源(不需要Count属性)
  4. 简单
  5. 代码:

    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)。

答案 4 :(得分: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个相同大小或接近相等大小的块。

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的倍数或其他位置的基础上对输出进行加扰。你没有得到原始顺序的单词。

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

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操作here

的等效方法

答案 5 :(得分: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;
        }

    }
}

享受!

答案 6 :(得分: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));
}

答案 7 :(得分:4)

有趣的主题。要获得Split / Partition的流式版本,可以使用枚举器并使用扩展方法从枚举器中生成序列。使用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);
    }
}

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

享受!

答案 8 :(得分: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这一系列问题中看到了一些答案。但我相信总的来说,这是一个在错误检查和效率方面更完整的解决方案。

答案 9 :(得分: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;
        }
    }

答案 10 :(得分: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;
    }      

}

答案 11 :(得分: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;
        }

答案 12 :(得分:0)

下面的代码返回给定数量的块以及已排序的数据

    static IEnumerable<IEnumerable<T>> SplitSequentially<T>(int chunkParts, List<T> inputList)
    {
        List<int> Splits = split(inputList.Count, chunkParts);

        var skipNumber = 0;
        List<List<T>> list = new List<List<T>>();
        foreach (var count in Splits)
        {
            var internalList = inputList.Skip(skipNumber).Take(count).ToList();
            list.Add(internalList);
            skipNumber += count;
        }
        return list;
    }
    static List<int> split(int x, int n)
    {
        List<int> list = new List<int>();

        if (x % n == 0)
        {
            for (int i = 0; i < n; i++)
                list.Add(x / n);
        }
        else
        {

            // upto n-(x % n) the values 
            // will be x / n 
            // after that the values 
            // will be x / n + 1 
            int zp = n - (x % n);
            int pp = x / n;
            for (int i = 0; i < n; i++)
            {

                if (i >= zp)
                    list.Add((pp + 1));
                else
                    list.Add(pp);
            }
        }
        return list;
    }

答案 13 :(得分: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))
        );
    }
}

答案 14 :(得分:0)

我正在寻找像字符串一样的分割,所以整个List按照一些规则进行分割,不仅是第一部分,这是我的解决方案

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

答案 15 :(得分: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;
  });

答案 16 :(得分: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

答案 17 :(得分: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&lt; IEnumerable&lt; int&gt;&gt;出于某种原因...

答案 18 :(得分:-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; }
    }
}

答案 19 :(得分:-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();