Divide a large IEnumerable into smaller IEnumerable of a fix amount of item

前端 未结 5 520
忘了有多久
忘了有多久 2020-12-18 01:35

In order to support an API that only accepts a specific amount of items (5 items), I want to transform a LINQ result into smaller groups of items that always contain that se

相关标签:
5条回答
  • 2020-12-18 02:12
    var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    var result = new List<List<int>>();
    while (list.Count != 0) {
        result.Add(list.TakeWhile(x => x++ <= 5).ToList());
        list.RemoveRange(0, list.Count < 5 ? list.Count : 5);
    }
    
    0 讨论(0)
  • 2020-12-18 02:22

    I'd just do something like this:

    public static IEnumerable<IEnumerable<T>> TakeChunks<T>(this IEnumerable<T> source, int size)
    {
        // Typically you'd put argument validation in the method call and then
        // implement it using a private method... I'll leave that to your
        // imagination.
    
        var list = new List<T>(size);
    
        foreach (T item in source)
        {
            list.Add(item);
            if (list.Count == size)
            {
                List<T> chunk = list;
                list = new List<T>(size);
                yield return chunk;
            }
        }
    
        if (list.Count > 0)
        {
            yield return list;
        }
    }
    

    Usage:

    var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    foreach (var chunk in list.TakeChunks(3))
    {
        Console.WriteLine(string.Join(", ", chunk));
    }
    

    Output:

    1, 2, 3
    4, 5, 6
    7, 8, 9
    10
    

    Rationale:

    Compared to other methods such multiple calls to Skip and Take or a big fancy LINQ query, the above is:

    • More efficient
    • More obvious in function (in my opinion)
    • More readable in implementation (again, in my opinion)
    0 讨论(0)
  • 2020-12-18 02:23

    We have a Batch method in MoreLINQ. You need to be careful how you use it, as the batch that is passed to the selector each time is a reference to the same array - but it does work.

    You can use GroupBy, but that can't be lazy - it has to accumulate all the results before it can return anything. That may be okay for you, but it's worth being aware of.

    0 讨论(0)
  • 2020-12-18 02:27

    One easy possibility is to use the Enumerable.Skip and Enumerable.Take methods, for example:

    List<int> nums = new List<int>(){1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18};
    
    var list1 = nums.Take(5);
    var list2 = nums.Skip(5).Take(5);
    var list3 = nums.Skip(10).Take(5);
    var list4 = nums.Skip(15).Take(5);
    

    As Jon mentioned in the comments though, a simple approach like this one will re-evaluate nums (in this example) each time, which will impact performance (depending on the size of the collection).

    0 讨论(0)
  • 2020-12-18 02:33

    Try something like this:

    var result = items.Select((value, index) => new { Index = index, Value = value})
                      .GroupBy(x => x.Index / 5)
                      .Select(g => g.Select(x => x.Value).ToList())
                      .ToList();
    

    It works by partitioning the items into groups based on their index in the original list.

    0 讨论(0)
提交回复
热议问题