Split C# collection into equal parts, maintaining sort

Viewed 12878

I am trying to split a collection into multiple collections while maintaining a sort I have on the collection. I have tried using the following extension method, but it breaks them incorrectly. Basically, if I was to look at the items in the collection, the order should be the same when compared to the broken up collections joined. Here is the code I am using that doesn't work:

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 = number of sub enumerables
9 Answers

This will do exactly as requested. It will also cater for uneven groupings i.e. 27 elements in to 10 groups will yield 7 groups of three and 3 groups of two

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

For demo purposes

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

the above demo yields

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

The code provided in the first answer splits a group of 21 elements to 10 with the following number of elements in the partitions: 3, 3, 3, 3, 3, 3, 3, 0, 0, 0

The wanted element number is: 3, 2, 2, 2, 2, 2, 2, 2, 2, 2

I made a small change in the provided code to give the wanted partition sizes

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