Split List into Sublists with LINQ

前端 未结 30 2388
灰色年华
灰色年华 2020-11-21 06:26

Is there any way I can separate a List into several separate lists of SomeObject, using the item index as the delimiter of each s

相关标签:
30条回答
  • 2020-11-21 07:03

    So performatic as the Sam Saffron's approach.

    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");
    
        return BatchImpl(source, size).TakeWhile(x => x.Any());
    }
    
    static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
    {
        var values = new List<T>();
        var group = 1;
        var disposed = false;
        var e = source.GetEnumerator();
    
        try
        {
            while (!disposed)
            {
                yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
                group++;
            }
        }
        finally
        {
            if (!disposed)
                e.Dispose();
        }
    }
    
    static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
    {
        var min = (group - 1) * size + 1;
        var max = group * size;
        var hasValue = false;
    
        while (values.Count < min && e.MoveNext())
        {
            values.Add(e.Current);
        }
    
        for (var i = min; i <= max; i++)
        {
            if (i <= values.Count)
            {
                hasValue = true;
            }
            else if (hasValue = e.MoveNext())
            {
                values.Add(e.Current);
            }
            else
            {
                dispose();
            }
    
            if (hasValue)
                yield return values[i - 1];
            else
                yield break;
        }
    }
    

    }

    0 讨论(0)
  • 2020-11-21 07:03

    If the source collection implements IList < T > (random access by index) we could use the below approach. It only fetches the elements when they are really accessed, so this is particularly useful for lazily-evaluated collections. Similar to unbounded IEnumerable< T >, but for IList< T >.

        public static IEnumerable<IEnumerable<T>> Chunkify<T>(this IList<T> src, int chunkSize)
        {
            if (src == null) throw new ArgumentNullException(nameof(src));
            if (chunkSize < 1) throw new ArgumentOutOfRangeException(nameof(chunkSize), $"must be > 0, got {chunkSize}");
    
            for(var ci = 0; ci <= src.Count/chunkSize; ci++){
                yield return Window(src, ci*chunkSize, Math.Min((ci+1)*chunkSize, src.Count)-1);
            }
        }
    
        private static IEnumerable<T> Window<T>(IList<T> src, int startIdx, int endIdx)
        {
            Console.WriteLine($"window {startIdx} - {endIdx}");
            while(startIdx <= endIdx){
                yield return src[startIdx++];
            }
        }
    
    0 讨论(0)
  • 2020-11-21 07:04

    Can work with infinite generators:

    a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
     .Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
     .Where((x, i) => i % 3 == 0)
    

    Demo code: https://ideone.com/GKmL7M

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    public class Test
    {
      private static void DoIt(IEnumerable<int> a)
      {
        Console.WriteLine(String.Join(" ", a));
    
        foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
          Console.WriteLine(String.Join(" ", x));
    
        Console.WriteLine();
      }
    
      public static void Main()
      {
        DoIt(new int[] {1});
        DoIt(new int[] {1, 2});
        DoIt(new int[] {1, 2, 3});
        DoIt(new int[] {1, 2, 3, 4});
        DoIt(new int[] {1, 2, 3, 4, 5});
        DoIt(new int[] {1, 2, 3, 4, 5, 6});
      }
    }
    
    1
    
    1 2
    
    1 2 3
    1 2 3
    
    1 2 3 4
    1 2 3
    
    1 2 3 4 5
    1 2 3
    
    1 2 3 4 5 6
    1 2 3
    4 5 6
    

    But actually I would prefer to write corresponding method without linq.

    0 讨论(0)
  • 2020-11-21 07:05

    Check this out! I have a list of elements with a sequence counter and date. For each time the sequence restarts, I want to create a new list.

    Ex. list of messages.

     List<dynamic> messages = new List<dynamic>
            {
                new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
                new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
                new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
    
                //restart of sequence
                new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
                new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
                new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
    
                //restart of sequence
                new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
                new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
                new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
            };
    

    I want to split the list into separate lists as the counter restarts. Here is the code:

    var arraylist = new List<List<dynamic>>();
    
            List<dynamic> messages = new List<dynamic>
            {
                new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
                new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
                new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
    
                //restart of sequence
                new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
                new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
                new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
    
                //restart of sequence
                new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
                new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
                new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
            };
    
            //group by FcntUp and CommTimestamp
            var query = messages.GroupBy(x => new { x.FcntUp, x.CommTimestamp });
    
            //declare the current item
            dynamic currentItem = null;
    
            //declare the list of ranges
            List<dynamic> range = null;
    
            //loop through the sorted list
            foreach (var item in query)
            {
                //check if start of new range
                if (currentItem == null || item.Key.FcntUp < currentItem.Key.FcntUp)
                {
                    //create a new list if the FcntUp starts on a new range
                    range = new List<dynamic>();
    
                    //add the list to the parent list
                    arraylist.Add(range);
                }
    
                //add the item to the sublist
                range.Add(item);
    
                //set the current item
                currentItem = item;
            }
    
    0 讨论(0)
  • 2020-11-21 07:07

    Just putting in my two cents. If you wanted to "bucket" the list (visualize left to right), you could do the following:

     public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
        {
            List<List<T>> result = new List<List<T>>();
            for (int i = 0; i < numberOfBuckets; i++)
            {
                result.Add(new List<T>());
            }
    
            int count = 0;
            while (count < source.Count())
            {
                var mod = count % numberOfBuckets;
                result[mod].Add(source[count]);
                count++;
            }
            return result;
        }
    
    0 讨论(0)
  • 2020-11-21 07:08

    Another way is using Rx Buffer operator

    //using System.Linq;
    //using System.Reactive.Linq;
    //using System.Reactive.Threading.Tasks;
    
    var observableBatches = anAnumerable.ToObservable().Buffer(size);
    
    var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();
    
    0 讨论(0)
提交回复
热议问题