将C#集合分成相等的部分,保持排序

13
我想将一个集合拆分成多个集合,同时保持我在集合上的排序。我尝试使用以下扩展方法,但它们错误地拆分了它们。基本上,如果我查看集合中的项目,则与连接的已拆分集合相比较时顺序应该是相同的。这是我正在使用但不起作用的代码:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
        {
            int i = 0;
            var splits = from name in list
                         group name by i++ % parts into part
                         select part.AsEnumerable();
            return splits;
        }
  • int parts = 子可枚举项的数量

1
@Kirk Woll:这不一样,在你提出的问题中,扩展方法在一个子可枚举中最多取最大元素数,而在这里,据我所知,我们有所需数量的子可枚举。 - Andrew Bezzub
@Andrew,你说得对,我明白你的观点。 - Kirk Woll
你能否提供一个例子,说明你得到了什么以及为什么是错误的。你想要像从一副牌中牌一样对它们进行分区,还是像将牌堆成几部分那样? - Ian Mercer
顺便提一下,你可以使用 .Select ((x, i) => ...) 而不是声明 i 并递增它。请参阅 http://msdn.microsoft.com/en-us/library/bb534869.aspx - Ian Mercer
9个回答

12

我需要利用这个功能将一个对象列表分组为每组4个,然后进行比较。它将保持原来的顺序。可以扩展到除“List”以外的其他内容。

/// <summary>
/// Partition a list of elements into a smaller group of elements
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="totalPartitions"></param>
/// <returns></returns>
public static List<T>[] Partition<T>(List<T> list, int totalPartitions)
{
    if (list == null)
        throw new ArgumentNullException("list");

    if (totalPartitions < 1)
        throw new ArgumentOutOfRangeException("totalPartitions");

    List<T>[] partitions = new List<T>[totalPartitions];

    int maxSize = (int)Math.Ceiling(list.Count / (double)totalPartitions);
    int k = 0;

    for (int i = 0; i < partitions.Length; i++)
    {
        partitions[i] = new List<T>();
        for (int j = k; j < k + maxSize; j++)
        {
            if (j >= list.Count)
                break;
            partitions[i].Add(list[j]);
        }
        k += maxSize;
    }

    return partitions;
}

这个算法没有bug吗..有人试过验证吗?在我看来,这行代码int maxSize = (int)Math.Ceiling(list.Count / (double)totalPartitions)可能会导致无效的maxsize计数..例如;一个包含21个元素要被分割成10组;实际上只会创建7组3个元素和3个空组..原因是大小计算将从2.1四舍五入到3。 - Dave Lawrence
是的,我想是这样的。如果我有一个字母表的列表,并且我将其分割成100个分区,那么我将得到一个对象,其中0-25填充了每个1个项目,而26-99将为空。 LINQ从3.5开始可能会解决这个问题,只是我几年没有看过这个了。它做到了我需要的,显然也是OP需要的。 - Christopher Klein

4

对于这个相对较旧的问题,有一种稍微更干净的 LINQ 方法:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int n)
{
    var count = source.Count();

    return source.Select((x, i) => new { value = x, index = i })
        .GroupBy(x => x.index / (int)Math.Ceiling(count / (double)n))
        .Select(x => x.Select(z => z.value));
}

1
这将创建大小不完全相等的分区。对于7个值,分区将具有3、3和1的计数。我需要我的分区计数为3、2和2。 - Dudeman3000

2

Jon Skeet的MoreLINQ库可能适合您:

https://code.google.com/p/morelinq/source/browse/MoreLinq/Batch.cs

var items = list.Batch(parts);  // gives you IEnumerable<IEnumerable<T>>
var items = list.Batch(parts, seq => seq.ToList()); // gives you IEnumerable<List<T>>
// etc...

另一个例子:
public class Program
{
    static void Main(string[] args)
    {
        var list = new List<int>();
        for (int i = 1; i < 10000; i++)
        {
            list.Add(i);
        }

        var batched = list.Batch(681);

        // will print 15. The 15th element has 465 items...
        Console.WriteLine(batched.Count().ToString());  
        Console.WriteLine(batched.ElementAt(14).Count().ToString());
        Console.WriteLine();
        Console.WriteLine("Press enter to exit.");
        Console.ReadLine();
    }
}

当我扫描批次的内容时,它们的顺序被保留了。


1
在这些示例中,它似乎希望“parts”是每个集合中的项目数量。在我的示例中,我希望“parts”是要创建的子集合的数量。 - Brian David Berman

1
    double partLength = list.Count() / (double)parts;

    int i = 0;
    var splits = from name in list
                 group name by Math.Floor((double)(i++ / partLength)) into part
                 select part;

在你的例子中,“splits” 变成了一个 IEnumerable<IGrouping<double, T>>,但它需要是一个 IEnumerable<IEnumerable<T>>。 - Brian David Berman
1
请纠正我,但是IGrouping<double, T>不是IEnumerable<T>吗? - kevev22

1
要将通用列表分割成相等的块,请使用以下通用方法。

 private IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(IEnumerable<T> list, int columnCount)
                {
                    var elementsCount = list.Count();
                    int rowCount = elementsCount / columnCount;
                    int noOfCells = elementsCount % columnCount;

                    int finalRowCount = rowCount;
                    if (noOfCells > 0)
                    {
                        finalRowCount++;
                    }

                    var toreturn = new List<IEnumerable<T>>();
                    var pushto = 0;
                    for (int j = 0; j < columnCount; j++)
                    {
                        var start = j;
                        int i = 0;
                        var end = i;
                        for (i = 0; i < finalRowCount; i++)
                        {
                            if ((i < rowCount) || ((i == rowCount) && (j < noOfCells)))
                            {
                                start = j;
                                end = i;
                            }
                        }
                        toreturn.Add(list.Skip(pushto).Take(end + 1));
                        pushto += end + 1;
                    }

                    return toreturn;

                }

List<int> recordNumbers = new List<int>() { 1, 2, 3, 4, 5, 6,7,8,9,10,11};

var splitedItems = SplitMaintainingOrder<int>(recordNumbers , 4);

Output will be:

List 1 : 1,2,3
List 2 : 4,5,6
List 3 : 7,8,9
List 4 : 10,11

~愉快的编码..



我认为这个答案更加适用,因为它平衡了子列表。例如,如果您想将其用于工作线程的负载平衡,您不必担心空闲的工作线程取决于您的数字。 - marceloatg

0

第一个答案提供的代码将21个元素分成10个分区,每个分区中的元素数量如下: 3, 3, 3, 3, 3, 3, 3, 0, 0, 0

所需的元素数量为: 3, 2, 2, 2, 2, 2, 2, 2, 2, 2

我对提供的代码进行了小改动,以得到所需的分区大小。

    public static List<T>[] Partition<T>(List<T> list, int totalPartitions)
    {            
        if (list == null)
            throw new ArgumentNullException("list");

        if (totalPartitions < 1)
            throw new ArgumentOutOfRangeException("totalPartitions");

        List<T>[] partitions = new List<T>[totalPartitions];            
        int k = 0;

        for (int i = 0; i < partitions.Length; i++)
        {
            int maxSize = (int)Math.Ceiling( (list.Count - i) / (double)totalPartitions);
            partitions[i] = new List<T>();
            for (int j = k; j < k + maxSize; j++)
            {
                if (j >= list.Count)
                    break;
                partitions[i].Add(list[j]);
            }
            k += maxSize;
        }
        return partitions;
    }

0
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int nGroups = (int)Math.Ceiling(list.Count() / (double)parts);

        var groups = Enumerable.Range(0, nGroups);

        return groups.Select(g => list.Skip(g * parts).Take(parts));
    }

0

这将完全按照要求执行。它还将适应不均匀的分组,即27个元素分成10组将产生7组三个和3组两个。

        public static IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(this IEnumerable<T> list, int parts)
    {
        if (list.Count() == 0) return Enumerable.Empty<IEnumerable<T>>();

        var toreturn = new List<IEnumerable<T>>();

        var splitFactor = Decimal.Divide((decimal)list.Count(), parts);
        int currentIndex = 0;

        for (var i = 0; i < parts; i++)
        {
            var toTake = Convert.ToInt32(
                i == 0 ? Math.Ceiling(splitFactor) : (
                    (Decimal.Compare(Decimal.Divide(Convert.ToDecimal(currentIndex), Convert.ToDecimal(i)), splitFactor) > 0) ? 
                        Math.Floor(splitFactor) : Math.Ceiling(splitFactor)));

            toreturn.Add(list.Skip(currentIndex).Take(toTake));
            currentIndex += toTake;
        }

        return toreturn;
    }

仅供演示目的

        [TestMethod]
    public void splitlist()
    {
        var list = new decimal[] { 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 };

        var splitlists = list.SplitMaintainingOrder(10);

        int i = 1;

        foreach (var group in splitlists)
        {
            Console.WriteLine("Group {0} elements {1}", i++, String.Join(",", group));                 
        }
    }

上述演示产生的结果如下:

Test Name:  splitlist
Test Outcome:   Passed
Result StandardOutput:  
Group 1 elements 1,2,3
Group 2 elements 4,5
Group 3 elements 6,7,8
Group 4 elements 9,10,11
Group 5 elements 12,13
Group 6 elements 14,15,16
Group 7 elements 17,18,19
Group 8 elements 20,21
Group 9 elements 22,23,24
Group 10 elements 25,26,27

0

据我理解,您想将可枚举对象分成若干等大小的部分,而不破坏元素的顺序。看起来唯一的选择是首先获取输入可枚举对象的长度,因此您需要至少两次迭代可枚举对象。


@Kirk Woll:这取决于需要什么。如果“parts”是一个可枚举对象中元素的数量,那么很容易解决。但是上面的代码让我想到,“parts”是所需子可枚举对象的数量,而不是可枚举对象中的元素数量。 - Andrew Bezzub
"parts" 是子枚举的数量。 - Brian David Berman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接