Split C# collection into equal parts, maintaining sort

前端 未结 8 1782
野的像风
野的像风 2021-02-19 18:31

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

相关标签:
8条回答
  • 2021-02-19 18:49

    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
    
    0 讨论(0)
  • 2021-02-19 18:58

    I had to make use of this to compare a list of objects to one another in groups of 4... it will keep the objects in the order that the original possessed. Could be expanded to do something other than '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;
    }
    
    0 讨论(0)
  • 2021-02-19 18:59

    A slightly more clean LINQ approach, for this rather old question:

    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));
    }
    
    0 讨论(0)
  • 2021-02-19 19:06

    Jon Skeet's MoreLINQ library might do the trick for you:

    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...
    

    Another example:

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

    When I scanned the contents of the batches, the ordering was preserved.

    0 讨论(0)
  • 2021-02-19 19:06
        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 讨论(0)
  • 2021-02-19 19:09
        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;
    
    0 讨论(0)
提交回复
热议问题