在x个较小的列表之间均匀地拆分项目列表

时间:2014-06-17 07:44:14

标签: c# linq list

我再次问这个问题,因为上次我问过它,它被错误地标记为重复。我这次将包含更多信息,这可能使我更容易理解我的需要(由于没有正确定义问题,这可能是我自己的错误。)

我正在尝试将通用类型的列表拆分为4个列表。为了简单和理解,我将在这个例子中使用一个整数列表,但这不应该有所作为。

我做了很多搜索,找到了多个答案,例如"Split List into Sublists with LINQ"using batch methods to split,我尝试了MoreLinq的Batch方法等等。这些建议适用于他们应该做的事情,但它们并不像我需要的那样工作。

如果我有一个包含以下元素的列表(1-25的整数):

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] 

然后我需要做的是制作4个列表,其中包含可变数量的元素,其中元素在同一列表中递增,而不是使用下一个元素跳转到下一个列表。

[ 1,  2,  3,  4,  5,  6,  7]
[ 8,  9, 10, 11, 12, 13, 14]
[15, 16, 17, 18, 19, 20, 21]
[20, 21, 22, 23, 24, 25]

当在链接的任何一个问题中使用解决方案时,以4“部分”作为参数,我得到这样的列表(这是元素跳转到下一个列表而不是列表的下一个元素的示例):

[1, 5,  9, 13, 17, 21, 25],
[2, 6, 10, 14, 18, 22, 26],
[3, 7, 11, 15, 19, 23, 27],
[4, 8, 12, 16, 20, 24]

或者这(与MoreLinq的批处理方法相同)

[ 1,  2,  3,  4],
[ 5,  6,  7,  8],
[ 9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
[25, 26, 27],

因此,第一个解决方案将列表拆分为4个列表,但是将元素放入错误的顺序。第二种解决方案按正确的顺序拆分列表,但不是正确的长度。在最后一个解决方案中,他获得了X个列表,每个列表中包含4个元素,其中我需要有4个列表,每个列表中包含X个元素。

7 个答案:

答案 0 :(得分:8)

您可以使用以下扩展方法在所需数量的子列表上拆分列表,并在第一个子列表中包含其他项目:

public static IEnumerable<List<T>> Split<T>(this List<T> source, int count)
{
    int rangeSize = source.Count / count;
    int firstRangeSize = rangeSize + source.Count % count;
    int index = 0;

    yield return source.GetRange(index, firstRangeSize);
    index += firstRangeSize;

    while (index < source.Count)
    {         
        yield return source.GetRange(index, rangeSize);
        index += rangeSize;
    }
}

给定输入

var list = Enumerable.Range(1, 25).ToList();
var result = list.Split(4);

结果是

[
  [ 1, 2, 3, 4, 5, 6, 7 ],
  [ 8, 9, 10, 11, 12, 13 ],
  [ 14, 15, 16, 17, 18, 19 ],
  [ 20, 21, 22, 23, 24, 25 ]
]

更新:此解决方案为每个范围添加了额外的项目

public static IEnumerable<List<T>> Split<T>(this List<T> source, int count)
{
    int rangeSize = source.Count / count;
    int additionalItems = source.Count % count;
    int index = 0;

    while (index < source.Count)
    {   
        int currentRangeSize = rangeSize + ((additionalItems > 0) ? 1 : 0);
        yield return source.GetRange(index, currentRangeSize);
        index += currentRangeSize;
        additionalItems--;
    }
}

答案 1 :(得分:2)

以下是基于IEnumerable<T>的另一种解决方案。它具有以下特点:

  • 始终产生batchCount项,如果源可枚举小于批量大小,则会产生空列表。
  • 在前面支持更大的列表(例如,当batchCount为2且大小为3时,结果的长度将为[2,1]。
  • 迭代IEnumerable的多次。这意味着如果在此处执行类似于Entity Framework的查询,则应该在某处调用AsEnumerable。

第一个示例针对List<T>

进行了优化
public static class BatchOperations
{
    public static IEnumerable<List<T>> Batch<T>(this List<T> items, int batchCount)
    {
        int totalSize = items.Count;
        int remain = totalSize % batchCount;
        int skip = 0;
        for (int i = 0; i < batchCount; i++)
        {
            int size = totalSize / batchCount + (i <= remain ? 1 : 0);
            if (skip + size > items.Count) yield return new List<T>(0);
            else yield return items.GetRange(skip, size);
            skip += size;
        }
    }

    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int batchCount)
    {
        int totalSize = items.Count();
        int remain = totalSize%batchCount;
        int skip = 0;
        for (int i = 0; i < batchCount; i++)
        {
            int size = totalSize/batchCount + (i <= remain ? 1 : 0);
            yield return items.Skip(skip).Take(size);
            skip += size;
        }
    }
}

答案 2 :(得分:2)

谢尔文的回答显然是最好的,但为了完整性,如果你不想因某种原因制作副本列表,你可以使用这个解决方案(也许是因为你刚才{{ 1}}作为输入):

IEnumerable<T>

我想这会比使用Lists慢得多,但它会使用更少的内存。

答案 3 :(得分:1)

        const int groupSize = 4;

        var items = new []{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};

        var currentGroupIndex=-1;

        var step1 = items.Select(a =>{
            if (++currentGroupIndex >= groupSize)
                currentGroupIndex = 0;
            return new {Group = currentGroupIndex, Value = a};
        }).ToArray();


        var step2 = step1.GroupBy(a => a.Group).Select(a => a.ToArray()).ToArray();

        var group1 = step2[0].Select(a => a.Value).ToArray();
        var group2 = step2[1].Select(a => a.Value).ToArray();
        var group3 = step2[2].Select(a => a.Value).ToArray();
        var group4 = step2[3].Select(a => a.Value).ToArray();

这样做首先引入一个计数器(currentGroupIndex),它从零开始,并将为列表中的每个元素递增。当组大小达到时,索引将重置为零 变量step1现在包含属于GroupValue属性的项目 然后在Group语句中使用GroupBy值。

答案 4 :(得分:0)

Take&amp;我认为Skip可能非常有用,但我个人喜欢使用Func来做出这些选择,让方法更灵活。

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

namespace Splitter
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> numbers = Enumerable.Range(1, 25).ToList();
            int groupCount = 4;

            var lists = numbers.Groupem(groupCount, (e, i) =>
            {
                // In what group do i wanna have this element.
                int divider = numbers.Count / groupCount;
                int overflow = numbers.Count % divider;
                int index = (i - overflow) / divider;
                return index < 0 ? 0 : index;
            });

            Console.WriteLine("numbers: {0}", numbers.ShowContent());

            Console.WriteLine("Parts");
            foreach (IEnumerable<int> list in lists)
            {
                Console.WriteLine("{0}", list.ShowContent());
            }
        }
    }

    public static class EnumerableExtensions
    {
        private static List<T>[] CreateGroups<T>(int size)
        {
            List<T>[] groups = new List<T>[size];

            for (int i = 0; i < groups.Length; i++)
            {
                groups[i] = new List<T>();
            }

            return groups;
        }

        public static void Each<T>(this IEnumerable<T> source, Action<T, int> action)
        {
            var i = 0;
            foreach (var e in source) action(e, i++);
        }

        public static IEnumerable<IEnumerable<T>> Groupem<T>(this IEnumerable<T> source, int groupCount, Func<T, int, int> groupPicker, bool failOnOutOfBounds = true)
        {
            if (groupCount <= 0) throw new ArgumentOutOfRangeException("groupCount", "groupCount must be a integer greater than zero.");

            List<T>[] groups = CreateGroups<T>(groupCount);

            source.Each((element, index) =>
            {
                int groupIndex = groupPicker(element, index);

                if (groupIndex < 0 || groupIndex >= groups.Length)
                {
                    // When allowing some elements to fall out, set failOnOutOfBounds to false
                    if (failOnOutOfBounds)
                    {
                        throw new Exception("Some better exception than this");
                    }
                }
                else
                {
                    groups[groupIndex].Add(element);
                }
            });

            return groups;
        }

        public static string ShowContent<T>(this IEnumerable<T> list)
        {
            return "[" + string.Join(", ", list) + "]";
        }
    }
}

答案 5 :(得分:0)

这个怎么样,包括参数检查,使用空集。

完成两次通过,应该很快,我没有经过测试。

public static IList<Ilist<T>> Segment<T>(
        this IEnumerable<T> source,
        int segments)
{
    if (segments < 1)
    {
        throw new ArgumentOutOfRangeException("segments");
    }

    var list = source.ToList();
    var result = new IList<T>[segments];

    // In case the source is empty.
    if (!list.Any())
    {
        for (var i = 0; i < segments; i++)
        {
            result[i] = new T[0];
        }

        return result;
    }

    int remainder;
    var segmentSize = Math.DivRem(list.Count, segments, out remainder);
    var postion = 0;
    var segment = 0;
    while (segment < segments)
    {
        var count = segmentSize;
        if (remainder > 0)
        {
            remainder--;
            count++;
        }

        result[segment] = list.GetRange(position, count);
        segment++;
        position += count;
    }

    return result;
}

答案 6 :(得分:0)

这是一个优化的轻量级 O(N) 扩展方法解决方案

public static void Bifurcate<T>(this IEnumerable<T> _list, int amountOfListsOutputted, IList<IList<T>> outLists)
{
    var list = _list;

    var index = 0;
    outLists = new List<IList<T>>(amountOfListsOutputted);

    for (int i = 0; i < amountOfListsOutputted; i++)
    {
        outLists.Add(new List<T>());
    }

    foreach (var item in list)
    {
        outLists[index % amountOfListsOutputted].Add(item);

        ++index;
    }
}

简单地使用它:

public static void Main()
{
    var list = new List<int>(1000);


    //Split into 2

    list.Bifurcate(2, out var twoLists);

    var splitOne = twoLists[0];
    var splitTwo = twoLists[1];

    // splitOne.Count == 500
    // splitTwo.Count == 500


    //Split into 3

    list.Bifurcate(3, out var threeLists);

    var _splitOne = twoLists[0];
    var _splitTwo = twoLists[1];
    var _splitThree = twoLists[2];

    // _splitOne == 334
    // _splitTwo = 333
    // _splitThree == 333
}