Разделить список на подсписки с помощью LINQ - PullRequest
352 голосов
/ 07 января 2009

Можно ли как-нибудь разделить List<SomeObject> на несколько отдельных списков SomeObject, используя индекс элемента в качестве разделителя для каждого разбиения?

Позвольте мне привести пример:

У меня есть List<SomeObject>, и мне нужно List<List<SomeObject>> или List<SomeObject>[], чтобы каждый из этих результирующих списков содержал группу из 3 элементов исходного списка (последовательно).

например:.

  • Исходный список: [a, g, e, w, p, s, q, f, x, y, i, m, c]

  • Результирующие списки: [a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]

Я бы также хотел, чтобы размер результирующих списков был параметром этой функции.

Ответы [ 27 ]

6 голосов
/ 07 января 2009

Вот процедура деления списка, которую я написал пару месяцев назад:

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}
5 голосов
/ 01 апреля 2015

Я считаю, что этот маленький фрагмент хорошо выполняет свою работу.

public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
    var offset = 0;

    while (offset < source.Count)
    {
        yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
        offset += chunkSize;
    }
}
5 голосов
/ 13 мая 2014

Это старый вопрос, но это то, чем я закончил; он перечисляет перечисляемое только один раз, но создает списки для каждого из разделов. Он не страдает от неожиданного поведения при вызове ToArray(), как это делают некоторые реализации:

    public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (chunkSize < 1)
        {
            throw new ArgumentException("Invalid chunkSize: " + chunkSize);
        }

        using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
        {
            IList<T> currentChunk = new List<T>();
            while (sourceEnumerator.MoveNext())
            {
                currentChunk.Add(sourceEnumerator.Current);
                if (currentChunk.Count == chunkSize)
                {
                    yield return currentChunk;
                    currentChunk = new List<T>();
                }
            }

            if (currentChunk.Any())
            {
                yield return currentChunk;
            }
        }
    }
4 голосов
/ 25 февраля 2015

Старый код, но это то, что я использовал:

    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        var toReturn = new List<T>(max);
        foreach (var item in source)
        {
            toReturn.Add(item);
            if (toReturn.Count == max)
            {
                yield return toReturn;
                toReturn = new List<T>(max);
            }
        }
        if (toReturn.Any())
        {
            yield return toReturn;
        }
    }
4 голосов
/ 09 апреля 2010

Мы нашли, что решение Дэвида Б. сработало лучше всего. Но мы адаптировали его к более общему решению:

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();
4 голосов
/ 13 декабря 2014

Это следующее решение - самое компактное, которое я мог придумать, это O (n).

public static IEnumerable<T[]> Chunk<T>(IEnumerable<T> source, int chunksize)
{
    var list = source as IList<T> ?? source.ToList();
    for (int start = 0; start < list.Count; start += chunksize)
    {
        T[] chunk = new T[Math.Min(chunksize, list.Count - start)];
        for (int i = 0; i < chunk.Length; i++)
            chunk[i] = list[start + i];

        yield return chunk;
    }
}
4 голосов
/ 13 августа 2013

А как насчет этого?

var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3

var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
                    .Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
                    .ToList();

Насколько я знаю, GetRange () является линейным с точки зрения количества взятых предметов. Так что это должно хорошо работать.

3 голосов
/ 07 января 2009

Если список имеет тип system.collections.generic, вы можете использовать метод «CopyTo», доступный для копирования элементов вашего массива в другие вложенные массивы. Вы указываете начальный элемент и количество элементов для копирования.

Вы также можете сделать 3 клона из исходного списка и использовать «RemoveRange» в каждом списке, чтобы уменьшить список до нужного размера.

Или просто создайте вспомогательный метод, который сделает это за вас.

2 голосов
/ 27 октября 2016

Это старое решение, но у меня был другой подход. Я использую Skip для перехода к желаемому смещению и Take для извлечения нужного количества элементов:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
                                                   int chunkSize)
{
    if (chunkSize <= 0)
        throw new ArgumentOutOfRangeException($"{nameof(chunkSize)} should be > 0");

    var nbChunks = (int)Math.Ceiling((double)source.Count()/chunkSize);

    return Enumerable.Range(0, nbChunks)
                     .Select(chunkNb => source.Skip(chunkNb*chunkSize)
                     .Take(chunkSize));
}
1 голос
/ 29 ноября 2018

Другой способ - использование Rx Оператор буфера

//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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...