Разбить коллекцию на n частей с помощью LINQ?

Есть ли хороший способ разбить коллекцию на части n с помощью LINQ? Конечно, не обязательно равномерно.

То есть я хочу разделить коллекцию на подколлекции, каждая из которых содержит подмножество элементов, причем последняя коллекция может быть разорвана.

С пометкой: вопрос не имеет ничего общего с asp.net. Пожалуйста, отметьте свои вопросы соответствующим образом.

user49572 13.01.2009 10:37

Как именно вы хотите, чтобы они разделились, если даже не (конечно, с учетом конца)?

Marc Gravell 13.01.2009 10:53

кто связался с этим вопросом? Джон это ты? :-) вдруг на все эти ответы :-)

Simon_Weaver 18.03.2009 00:19

Связанный вопрос Разделить список на подсписки с LINQ

Gennady Vanin Геннадий Ванин 12.04.2013 06:47

@Simon_Weaver Я попытался уточнить, о чем вы спрашиваете, на основе принятого ответа. Фактически, существует множество способов «разбить» список, включая разложение каждого элемента списка на его элементы и помещение их в так называемые «параллельные» списки.

jpaugh 20.05.2017 22:36
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
122
5
79 145
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

Обновлено: Хорошо, похоже, я неправильно понял вопрос. Я прочитал это как «кусочки длины n», а не «n штук». Дох! Рассмотрение вопроса об удалении ответа ...

(Оригинальный ответ)

Я не верю, что есть встроенный способ разделения, хотя я намерен написать его в своем наборе дополнений к LINQ to Objects. У Марка Гравелла есть реализация здесь, хотя я бы, вероятно, изменил его, чтобы он возвращал представление только для чтения:

public static IEnumerable<IEnumerable<T>> Partition<T>
    (this IEnumerable<T> source, int size)
{
    T[] array = null;
    int count = 0;
    foreach (T item in source)
    {
        if (array == null)
        {
            array = new T[size];
        }
        array[count] = item;
        count++;
        if (count == size)
        {
            yield return new ReadOnlyCollection<T>(array);
            array = null;
            count = 0;
        }
    }
    if (array != null)
    {             
        Array.Resize(ref array, count);
        yield return new ReadOnlyCollection<T>(array);
    }
}

Вам, В самом деле, не нравятся эти "array [count ++]", а ;-p

Marc Gravell 13.01.2009 10:54

По иронии судьбы, я вполне мог использовать его еще до вчерашнего дня. Но, написав этот ответ, он был бы лицемерным - и, глядя на обе версии, я думаю, что этот является немного легче читать с первого взгляда. Сначала я использовал вместо него список - Добавить еще удобнее :)

Jon Skeet 13.01.2009 11:03

Ну, я просто добавил немного другой ответ (чтобы адресовать «n частей», а не «частей длины n») и смешал части вашей версии ;-p

Marc Gravell 13.01.2009 11:09

конечно, на этот раз мне понадобилось 8 штук, а не 8 :-), и это отлично сработало

Simon_Weaver 21.10.2011 12:37

Спасибо, что не удалили, хотя это не ответ на OP, я хотел то же самое - кусочки длины n :).

Gishu 06.12.2012 08:57

@Jon, мне просто интересно, почему в этом случае вы предпочитаете ReadOnlyCollectionмассив только для чтения?

CodeFox 07.02.2014 20:02

@Jon: Ах - хороший момент! Я имел в виду фиксированную длину массивов, но, конечно, в отличие от ReadOnlyCollection, элементы можно было заменить. Спасибо за ответ.

CodeFox 10.02.2014 13:37

@Gishu - В самом деле - это именно то, что мне было нужно!

Frank Tzanabetis 21.03.2014 06:35

Проблема с этим подходом заключается в том, что он просматривает полный список источников (требующий, чтобы весь список был в памяти), прежде чем двигаться дальше. Кто-нибудь знает лучшее решение для больших перечислений?

Dejan 02.04.2014 15:33

@Dejan: Нет, это не так. Обратите внимание на использование yield return. Для этого требуется, чтобы в памяти находился один партия, но это все.

Jon Skeet 02.04.2014 15:38

Да, я видел yield return, и, конечно, мой комментарий «необходимо просмотреть полный список» явно неверен. Простите. Произошло следующее: я пытался использовать метод в Parallel.ForEach, чтобы разделить мои данные, что привело к увеличению использования памяти. Я предполагаю, что TPL порождает слишком много задач, потому что yield return приходит только с после, заполняющим раздел, и поэтому на первом этапе будет просто подготовка разделов параллельно. Думаю, мне нужна правильная реализация msdn.microsoft.com/en-us/library/dd381768(VS.100).aspx.

Dejan 03.04.2014 13:25

@Dejan: Верно - я бы не хотел гадать, как он взаимодействует с параллельным секционированием LINQ, если честно :)

Jon Skeet 03.04.2014 13:40

Буферизация - это недостаток, если у вас большие куски. Думаю, лучшее решение здесь (Йеппе Стиг Нильсен): stackoverflow.com/questions/13709626/…

SalientBrain 27.08.2014 02:28

@SalientBrain: Гм, решение Джеппе Стига Нильсена по-прежнему использует пакетную обработку - обратите внимание на вызов ToList в yield return GetNextBatch(enumerator).ToList();. Пакетная обработка - это нужно, иначе вы получите очень странные результаты, если пропустите один раздел.

Jon Skeet 27.08.2014 09:49

@Jon Skeet: Ага, я это пропустил)) Так что никакой магии (. То же самое, другими словами. Спасибо.

SalientBrain 27.08.2014 16:59

Если порядок в этих частях не очень важен, вы можете попробовать следующее:

int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = 3;

var result =
   array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value);

// or
var result2 =
   from i in array.Select((value, index) => new { Value = value, Index = index })
   group i.Value by i.Index % n into g
   select g;

Однако по какой-то причине они не могут быть преобразованы в IEnumerable <IEnumerable <int>> ...

Это может быть сделано. Вместо прямого литья просто сделайте функцию универсальной, а затем вызовите ее для своего массива int

nawfal 07.12.2012 05:19
Ответ принят как подходящий

Чистый linq и самое простое решение показано ниже.

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}

Вы можете: выбрать part.AsEnumerable () вместо select (IEnumerable <T>) part. Он кажется более элегантным.

tuinstoel 11.02.2009 13:30

Спасибо за предложение tuinstoel. Теперь это выглядит лучше.

Muhammad Hasan Khan 12.02.2009 07:36

Выполнение всех этих операций с модулями для длинных списков может оказаться немного дорогостоящим.

Jonathan Allen 17.03.2009 21:11

Было бы лучше использовать перегрузку Select, которая включает index.

Marc Gravell 18.08.2009 01:21

Я добавил ответ, в котором используется синтаксис выбора перегрузки и цепочки методов.

reustmd 14.03.2011 23:21

@JonathanAllen См. Немодульный ответ stackoverflow.com/questions/438188/…

Muhammad Hasan Khan 14.11.2011 12:44

@HasanKhan: Что делать, если я не знаю, на сколько частей я его разбиваю. Я имею в виду, разбить на группы, скажем, по 3 человека.

Robin Maben 29.11.2011 17:30

Я не работаю, у меня есть только одна сплит-секция со счетом 1, и все мои предметы находятся внутри этой одной позиции, есть идеи?

Lucas Rodrigues Sena 22.01.2015 22:36

@LucasRodriguesSena Этот метод просит вас нет. разделений, а не то, сколько вы хотите в каждом разделении. Вы можете получить отказ. разделений, разделив желаемый размер на общее количество пунктов.

Muhammad Hasan Khan 23.01.2015 18:26
.AsEnumerable() не требуется, IGrouping <T> уже является IEnumerable <T>.
Alex 22.07.2015 20:28

@Alex Если вы возвращаете часть напрямую, возвращаемым типом будет IEnumerable <IGrouping <T>> вместо IEnumerable <IEnumerable <T>>

Muhammad Hasan Khan 23.07.2015 20:01

@HasanKhan Я не запускал код, но попробовал, и он отлично компилируется. Я думаю, поскольку возвращаемый тип явно IEnumerable<IEnumerable<T>>, при возврате части происходит неявное приведение. Нет?

Alex 23.07.2015 22:09

@ Алекс, верно. Когда я писал это, в C# не было ковариации.

Muhammad Hasan Khan 24.07.2015 02:29

@HasanKhan, ничего страшного. Явность кода обычно - это хорошо.

Alex 24.07.2015 23:29

Есть ли способ получить результат в виде IDictionary<int, IEnumerable<T>> вместо этого, используя модуль в качестве ключа?

PPC 01.11.2016 19:44

Для VB.NET у меня возникли трудности с синтаксисом запроса из-за отсутствия i ++ в VB (и я боролся с ключевым словом Into), поэтому вместо этого я использовал синтаксис метода: Dim i As Integer = 0 Dim splits As IEnumerable(Of IEnumerable(Of T)) = list.GroupBy(Function(item) Dim groupRemainder As Integer = i Mod parts i += 1 Return groupRemainder End Function).Select(Function(group) group.AsEnumerable())

David French 19.07.2017 16:02
int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 };

int itemIndex = 0;
int groupSize = 2;
int nextGroup = groupSize;

var seqItems = from aItem in items
               group aItem by 
                            (itemIndex++ < nextGroup) 
                            ? 
                            nextGroup / groupSize
                            :
                            (nextGroup += groupSize) / groupSize
                            into itemGroup
               select itemGroup.AsEnumerable();

Это мой код, красивый и короткий.

 <Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T))
     Dim result As New List(Of List(Of T))
     For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1
         result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size)))))
     Next
     Return result
 End Function

Я использую это:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize)
{
    return instance
        .Select((value, index) => new { Index = index, Value = value })
        .GroupBy(i => i.Index / partitionSize)
        .Select(i => i.Select(i2 => i2.Value));
}

Объясните, пожалуйста, почему. Я без проблем пользуюсь этой функцией!

Elmer 02.12.2011 06:58

прочитайте вопрос еще раз и посмотрите, получите ли вы n (почти) частей одинаковой длины с вашей функцией

Muhammad Hasan Khan 02.12.2011 12:13

@Elmer, ваш ответ правильный, но вопрос неправильный. Ваш ответ дает неизвестное количество фрагментов с фиксированным размером для каждого фрагмента (точно так же, как Раздел, имя, которое вы ему дали). Но OP хочет функциональность Split, которая дает фиксированное количество фрагментов любого размера на фрагмент (надеюсь, равных или близких к одинаковым размерам). Возможно здесь больше подходит stackoverflow.com/questions/3773403/…

nawfal 06.12.2012 18:36

Я думаю, вы можете просто изменить i.Index / partitionSize на i.Index% partitionSize и получить запрошенный результат. Я также предпочитаю это принятому ответу, поскольку он более компактный и читаемый.

Jake Drew 28.07.2015 19:45

Я довольно часто использовал функцию разделения, которую опубликовал ранее. Единственное, что было плохо, это то, что он не транслировался полностью. Это не проблема, если вы работаете с несколькими элементами в своей последовательности. Мне понадобилось новое решение, когда я начал работать с более чем 100 000 элементов в своей последовательности.

Следующее решение намного сложнее (и больше кода!), Но очень эффективно.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace LuvDaSun.Linq
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
        {
            /*
            return enumerable
                .Select((item, index) => new { Item = item, Index = index, })
                .GroupBy(item => item.Index / partitionSize)
                .Select(group => group.Select(item => item.Item)                )
                ;
            */

            return new PartitioningEnumerable<T>(enumerable, partitionSize);
        }

    }


    class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        IEnumerable<T> _enumerable;
        int _partitionSize;
        public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
        {
            _enumerable = enumerable;
            _partitionSize = partitionSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
            _enumerator.Dispose();
        }

        IEnumerable<T> _current;
        public IEnumerable<T> Current
        {
            get { return _current; }
        }
        object IEnumerator.Current
        {
            get { return _current; }
        }

        public void Reset()
        {
            _current = null;
            _enumerator.Reset();
        }

        public bool MoveNext()
        {
            bool result;

            if (_enumerator.MoveNext())
            {
                _current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
                result = true;
            }
            else
            {
                _current = null;
                result = false;
            }

            return result;
        }

    }



    class PartitionEnumerable<T> : IEnumerable<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new PartitionEnumerator<T>(_enumerator, _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitionEnumerator<T> : IEnumerator<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        int _count;
        public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
        }

        public T Current
        {
            get { return _enumerator.Current; }
        }
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }
        public void Reset()
        {
            if (_count > 0) throw new InvalidOperationException();
        }

        public bool MoveNext()
        {
            bool result;

            if (_count < _partitionSize)
            {
                if (_count > 0)
                {
                    result = _enumerator.MoveNext();
                }
                else
                {
                    result = true;
                }
                _count++;
            }
            else
            {
                result = false;
            }

            return result;
        }

    }
}

Наслаждаться!

Эта версия нарушает договор IEnumerator. Неверно выбрасывать InvalidOperationException при вызове Reset - я считаю, что многие методы расширения LINQ полагаются на это поведение.

ShadowChaser 06.05.2011 21:05

@ShadowChaser Я думаю, что Reset () должен выбросить NotSupportedException, и все будет в порядке. Из документации MSDN: «Метод Reset предоставляется для взаимодействия с COM. Его не обязательно реализовывать; вместо этого разработчик может просто выбросить NotSupportedException».

toong 17.05.2013 16:25

@toong Вау, ты прав. Не знаю, как я это пропустил после всего этого времени.

ShadowChaser 27.05.2013 19:38

Глючит! Я точно не помню, но (насколько я помню) он выполняет нежелательный шаг и может привести к уродливым побочным эффектам (например, с помощью datareader). Лучшее решение здесь (Джеппе Стиг Нильсен): stackoverflow.com/questions/13709626/…

SalientBrain 27.08.2014 02:24

Хорошо, я брошу шляпу на ринг. Преимущества моего алгоритма:

  1. Отсутствие дорогостоящих операторов умножения, деления или модуля
  2. Все операции выполняются за O (1) (см. Примечание ниже)
  3. Работает для источника IEnumerable <> (свойство Count не требуется)
  4. Простой

Код:

public static IEnumerable<IEnumerable<T>>
  Section<T>(this IEnumerable<T> source, int length)
{
  if (length <= 0)
    throw new ArgumentOutOfRangeException("length");

  var section = new List<T>(length);

  foreach (var item in source)
  {
    section.Add(item);

    if (section.Count == length)
    {
      yield return section.AsReadOnly();
      section = new List<T>(length);
    }
  }

  if (section.Count > 0)
    yield return section.AsReadOnly();
}

Как указано в комментариях ниже, этот подход фактически не решает исходный вопрос, который требовал фиксированного количества разделов примерно одинаковой длины. Тем не менее, вы все еще можете использовать мой подход для решения исходного вопроса, назвав его так:

myEnum.Section(myEnum.Count() / number_of_sections + 1)

При таком использовании подход больше не O (1), поскольку операция Count () равна O (N).

Великолепно - лучшее решение здесь! Несколько оптимизаций: * Очистите связанный список вместо создания нового для каждого раздела. Ссылка на связанный список никогда не возвращается вызывающему, поэтому это полностью безопасно. * Не создавайте связанный список, пока не дойдете до первого элемента - в этом случае распределение не будет, если источник пуст

ShadowChaser 06.05.2011 21:24

@ShadowChaser Согласно MSDN очистка LinkedList - это сложность O (N), поэтому это разрушит мою цель O (1). Конечно, вы можете утверждать, что foreach - это O (N) для начала ... :)

Mike 09.05.2011 19:31

ваш ответ правильный, но вопрос неправильный. Ваш ответ дает неизвестное количество фрагментов с фиксированным размером для каждого фрагмента. Но OP хочет функциональность Split, которая дает фиксированное количество фрагментов любого размера на фрагмент (надеюсь, равных или близких к одинаковым размерам). Возможно здесь больше подходит stackoverflow.com/questions/3773403/…

nawfal 06.12.2012 18:35

@nawfal - согласен. Я подошел к этому так, потому что он не требует заранее знать длину перечислимого, что более эффективно для некоторых приложений. Если вам нужно фиксированное количество фрагментов, вы можете вызвать myEnum.Section (myEnum.Count () / number_of_chunks + 1). Я обновлю свой ответ, чтобы отразить это.

Mike 07.12.2012 00:01

@Mike, зачем нужны связанные списки, отдельная статическая функция и т. д.? Почему бы не использовать простой подход, который, как мне кажется, делает функцию еще быстрее? В предыдущая ветка, которую я опубликовал, вот мой ответ, который представляет собой аналогичную реализацию, которая работает быстрее и без проблем со структурами сбора.

nawfal 07.12.2012 05:24

@nawfal - мне нужен был подход O (1) или «постоянное время». Skip () внутри вашего цикла делает ваш подход O (N), потому что время, необходимое для обработки пропуска, увеличивается линейно с количеством элементов. Рассмотрим перечислимое число с 1e12 элементами, разделенными на группы по два - чем дальше вы попадете в перечислимое, тем медленнее оно будет. Я также пытался избежать умножения, которое обходится дорого по сравнению с другими операциями.

Mike 07.12.2012 19:49

@ Майк, ты проверил это? Надеюсь, вы знаете, что O (1) не означает быстрее, это означает только то, что время, необходимое для разделения, не масштабируется. Мне просто интересно, каково ваше обоснование слепо придерживаться O (1), когда оно может быть медленнее, чем другие O (n) для всех сценариев реальной жизни. Я даже проверил его на сумасшедший список прочности 10 ^ 8, и мой оказался еще быстрее. Надеюсь, вы знаете, что не существует даже стандартных типов коллекций, которые могут содержать 10 ^ 12 элементов ..

nawfal 08.12.2012 14:25

Несколько моментов: 1) почему вы продолжаете настаивать на том, что операции умножения / по модулю и т. д. Дороги? 2) myEnum.Section(myEnum.Count() / number_of_sections + 1) перечисляет первую коллекцию (и для данного вопроса может быть неэффективным), поэтому, пожалуйста, отредактируйте преимущества, которые вы упомянули для своего подхода.

nawfal 08.12.2012 15:41

3) Вы можете использовать облегченную структуру коллекции вместо LinkedList <T>, поскольку в ответах Джона Скита или Брэда они, как правило, работают быстрее. Как я уже сказал, O (1) не гарантирует скорости. Возможные накладные расходы с LinkedList перевешивают его выгоды (они предназначены для случайного удаления и вставки). Смотрите это ссылка здесь

nawfal 08.12.2012 15:45

@nawfal - Спасибо за подробный анализ, он меня держит в тонусе. Связанные списки в целом известны эффективными концевыми вставками, поэтому я выбрал их здесь. Однако я только что проверил его, и действительно, List <> намного быстрее. Я подозреваю, что это какая-то деталь реализации .NET, возможно, заслуживающая отдельного вопроса StackOverflow. Я изменил свой ответ, чтобы использовать List <> в соответствии с вашим предложением. Предварительное выделение емкости списка гарантирует, что конечная вставка по-прежнему будет O (1) и соответствует моей первоначальной цели дизайна. Я также перешел на встроенный .AsReadOnly () в .NET 4.5.

Mike 10.12.2012 19:58

@nawfal - На уровне машинного кода mult / div / mod обычно намного дороже (на порядок), чем простые операции, такие как set / get / compare. Конечно, это зависит от процессора, компилятора и реализации, так что вам действительно стоит провести сравнительный анализ вашего конкретного случая, если эта операция секционирования создает узкое место.

Mike 10.12.2012 20:09

Извините, это может быть глупый вопрос, но зачем нам здесь вызов .AsReadOnly ()?

ironic 05.09.2013 12:24

Технически вам это не нужно, но это своего рода «лучшая практика», поскольку мы возвращаем IEnumerable <>, который подразумевает, что он неизменяемый.

Mike 05.09.2013 21:09

Интересная ветка. Чтобы получить потоковую версию Split / Partition, можно использовать перечислители и вывести последовательности из перечислителя с использованием методов расширения. Преобразование императивного кода в функциональный с помощью yield - действительно очень мощный метод.

Сначала расширение перечислителя, которое превращает количество элементов в ленивую последовательность:

public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
    while (count > 0)
    {
        yield return enumerator.Current;
        if (--count > 0 && !enumerator.MoveNext()) yield break;
    }
}

И затем перечислимое расширение, которое разбивает последовательность:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
    var enumerator = seq.GetEnumerator();

    while (enumerator.MoveNext())
    {
        yield return enumerator.TakeFromCurrent(partitionSize);
    }
}

Конечным результатом является высокоэффективная потоковая и ленивая реализация, основанная на очень простом коде.

Наслаждаться!

Изначально я запрограммировал то же самое, но шаблон ломается, когда Reset вызывается в одном из вложенных экземпляров IEnumerable <T>.

ShadowChaser 06.05.2011 21:13

Это все еще работает, если вы перечисляете только раздел, а не внутреннее перечисление? поскольку внутренний перечислитель отложен, ни один код для внутреннего (взять из текущего) не будет выполняться до тех пор, пока он не будет перечислен, поэтому movenext () будет вызываться только функцией внешнего раздела, верно? Если мои предположения верны, то это потенциально может привести к n разделам с n элементами в исходном перечислимом, а внутренние перечисления дадут неожиданные результаты.

Brad 16.11.2011 23:11

@Brad, как и следовало ожидать, он "выйдет из строя", аналогично некоторым проблемам в этом потоке stackoverflow.com/questions/419019/… (в частности, stackoverflow.com/a/20953521/1037948)

drzaus 10.06.2015 23:00
static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
            return list.Select((item, index) => new {index, item})
                       .GroupBy(x => x.index % parts)
                       .Select(x => x.Select(y => y.item));
    }
}

У меня иррациональная неприязнь к Linq в стиле SQL, так что это мой любимый ответ.

piedar 02.01.2014 23:53

@ manu08, я пробовал ур код, у меня есть список var dept = {1,2,3,4,5}. После разделения результат будет похож на dept1 = {1,3,5} и dept2 = { 2,4 }, где parts = 2. Но результат, который мне нужен, это dept1 = {1,2,3} и dept2 = {4,5}.

Karthik Arthik 13.01.2016 09:09

У меня была такая же проблема с модулем, поэтому я рассчитал длину столбца с помощью int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);, а затем сделал деление с помощью .GroupBy(x => x.index / columnLength). Один из недостатков - Count () перечисляет список.

goodeye 03.02.2017 03:09

Этот метод (и ему подобные) полагается на то, что размер списка больше, чем количество частей, на которые он разбивается.

Edward Brey 07.03.2021 15:33

Это эффективно с точки зрения памяти и максимально откладывает выполнение (на пакет) и работает за линейное время O (n).

    public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize)
    {
        List<T> batch = new List<T>(batchSize);
        foreach (var item in items)
        {
            batch.Add(item);

            if (batch.Count >= batchSize)
            {
                yield return batch;
                batch = new List<T>();
            }
        }

        if (batch.Count != 0)
        {
            //can't be batch size or would've yielded above
            batch.TrimExcess();
            yield return batch;
        }
    }

Это то же самое, что и принятый ответ, но в гораздо более простом представлении:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

Вышеупомянутый метод разбивает IEnumerable<T> на N частей равного или почти равного размера.

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

Вышеупомянутый метод разбивает IEnumerable<T> на фрагменты желаемого фиксированного размера, при этом общее количество фрагментов не имеет значения - вопрос не в этом.

Проблема с методом Split, помимо того, что он медленнее, заключается в том, что он скремблирует вывод в том смысле, что группировка будет выполняться на основе i-го числа, кратного N для каждой позиции, или, другими словами, вы не получаете куски в исходном порядке.

Почти каждый ответ здесь либо не сохраняет порядок, либо касается разбиения, а не разбиения, либо явно неверен. Попробуйте это быстрее, с сохранением порядка, но немного более подробным:

public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items, 
                                                   int numberOfChunks)
{
    if (numberOfChunks <= 0 || numberOfChunks > items.Count)
        throw new ArgumentOutOfRangeException("numberOfChunks");

    int sizePerPacket = items.Count / numberOfChunks;
    int extra = items.Count % numberOfChunks;

    for (int i = 0; i < numberOfChunks - extra; i++)
        yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);

    int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
    int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
    for (int i = 0; i < extra; i++)
        yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}

Эквивалентный метод для операции Partitionздесь

Только что наткнулся на эту ветку, и большинство решений здесь включают добавление элементов в коллекции, эффективно материализуя каждую страницу перед ее возвратом. Это плохо по двум причинам: во-первых, если ваши страницы большие, есть накладные расходы на память для заполнения страницы, во-вторых, есть итераторы, которые делают недействительными предыдущие записи при переходе к следующей (например, если вы обертываете DataReader в методе перечислителя) .

В этом решении используются два вложенных метода перечислителя, чтобы избежать необходимости кэшировать элементы во временные коллекции. Поскольку внешний и внутренний итераторы проходят по одному и тому же перечислимому элементу, они обязательно используют один и тот же перечислитель, поэтому важно не продвигать внешний итераторы, пока вы не закончите обработку текущей страницы. Тем не менее, если вы решите не перебирать всю текущую страницу, когда вы перейдете на следующую страницу, это решение автоматически перейдет к границе страницы.

using System.Collections.Generic;

public static class EnumerableExtensions
{
    /// <summary>
    /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
    /// </summary>
    /// <typeparam name = "T">The element type</typeparam>
    /// <param name = "enumerable">The source enumerable</param>
    /// <param name = "pageSize">The number of elements to return in each page</param>
    /// <returns></returns>
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
    {
        var enumerator = enumerable.GetEnumerator();

        while (enumerator.MoveNext())
        {
            var indexWithinPage = new IntByRef { Value = 0 };

            yield return SubPartition(enumerator, pageSize, indexWithinPage);

            // Continue iterating through any remaining items in the page, to align with the start of the next page
            for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
            }
        }
    }

    private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
    {
        for (; index.Value < pageSize; index.Value++)
        {
            yield return enumerator.Current;

            if (!enumerator.MoveNext())
            {
                yield break;
            }
        }
    }

    private class IntByRef
    {
        public int Value { get; set; }
    }
}

Это вообще не работает! Лучшее из возможных - здесь stackoverflow.com/questions/13709626/…! См. Комментарии.

SalientBrain 27.08.2014 02:09

Это мой способ: перечислять элементы и разбивать строки по столбцам.

  int repat_count=4;

  arrItems.ForEach((x, i) => {
    if (i % repat_count == 0) 
        row = tbo.NewElement(el_tr, cls_min_height);
    var td = row.NewElement(el_td);
    td.innerHTML = x.Name;
  });
   protected List<List<int>> MySplit(int MaxNumber, int Divider)
        {
            List<List<int>> lst = new List<List<int>>();
            int ListCount = 0;
            int d = MaxNumber / Divider;
            lst.Add(new List<int>());
            for (int i = 1; i <= MaxNumber; i++)
            {
                lst[ListCount].Add(i);
                if (i != 0 && i % d == 0)
                {
                    ListCount++;
                    d += MaxNumber / Divider;
                    lst.Add(new List<int>());
                }
            }
            return lst;
        }

Я искал разделение, подобное тому, что со строкой, поэтому весь список разделен по какому-то правилу, а не только первая часть, это мое решение

List<int> sequence = new List<int>();
for (int i = 0; i < 2000; i++)
{
     sequence.Add(i);
}
int splitIndex = 900;
List<List<int>> splitted = new List<List<int>>();
while (sequence.Count != 0)
{
    splitted.Add(sequence.Take(splitIndex).ToList() );
    sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count));
}

В следующий раз попробуйте: var nrs = Enumerable.Range (1,2000) .ToList ();

MBoros 18.02.2017 11:39

Вот небольшая настройка количества элементов вместо количества частей:

public static class MiscExctensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems)
    {
        return (
            list
            .Select((o, n) => new { o, n })
            .GroupBy(g => (int)(g.n / nbItems))
            .Select(g => g.Select(x => x.o))
        );
    }
}

На этот вопрос (и его двоюродных братьев) есть много отличных ответов. Мне самому это было нужно, и я создал решение, которое должно быть эффективным и устойчивым к ошибкам в сценарии, когда исходную коллекцию можно рассматривать как список. Он не использует ленивую итерацию, поэтому может не подходить для коллекций неизвестного размера, которые могут оказывать давление на память.

static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize)
{
    IList<T[]> result = null;
    if (source != null && batchsize > 0)
    {
        var list = source as List<T> ?? source.ToList();
        if (list.Count > 0)
        {
            result = new List<T[]>();
            for (var index = 0; index < list.Count; index += batchsize)
            {
                var rangesize = Math.Min(batchsize, list.Count - index);
                result.Add(list.GetRange(index, rangesize).ToArray());
            }
        }
    }
    return result ?? Enumerable.Empty<T[]>().ToList();
}

static public void TestGetChunks()
{
    var ids = Enumerable.Range(1, 163).Select(i => i.ToString());
    foreach (var chunk in ids.GetChunks(20))
    {
        Console.WriteLine("[{0}]", String.Join(",", chunk));
    }
}

Я видел несколько ответов по этому семейству вопросов, в которых используются GetRange и Math.Min. Но я считаю, что в целом это более полное решение с точки зрения проверки ошибок и эффективности.

Отличные ответы, для своего сценария я проверил принятый ответ, и, похоже, он не соблюдает порядок. Есть также отличный ответ от Nawfal, который поддерживает порядок. Но в моем сценарии я хотел разделить остаток нормализованным способом, все ответы, которые я видел, распределяли остаток или в начале или в конце.

В моем ответе остальная часть распределяется более нормализованным образом.

 static class Program
{          
    static void Main(string[] args)
    {
        var input = new List<String>();
        for (int k = 0; k < 18; ++k)
        {
            input.Add(k.ToString());
        }
        var result = splitListIntoSmallerLists(input, 15);            
        int i = 0;
        foreach(var resul in result){
            Console.WriteLine("------Segment:" + i.ToString() + "--------");
            foreach(var res in resul){
                Console.WriteLine(res);
            }
            i++;
        }
        Console.ReadLine();
    }

    private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists)
    {
        if (i_numberOfSmallerLists <= 0)
            throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists");

        int normalizedSpreadRemainderCounter = 0;
        int normalizedSpreadNumber = 0;
        //e.g 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2          
        int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists;                        
        int remainder = i_bigList.Count % i_numberOfSmallerLists;
        int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder;
        //In case remainder > 0 we want to spread the remainder equally between the others         
        if (remainder > 0)
        {
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder);    
            }
            else
            {
                normalizedSpreadNumber = 1;
            }   
        }
        List<List<T>> retVal = new List<List<T>>(outputSize);
        int inputIndex = 0;            
        for (int i = 0; i < outputSize; ++i)
        {
            retVal.Add(new List<T>());
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList));
                inputIndex += minimumNumberOfPartsInEachSmallerList;
            }
            //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber.
            if (remainder > 0)
            {
                if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1)
                {
                    retVal[i].Add(i_bigList[inputIndex]);
                    remainder--;
                    inputIndex++;
                    normalizedSpreadRemainderCounter=0;
                }
                else
                {
                    normalizedSpreadRemainderCounter++;
                }
            }
        }
        return retVal;
    }      

}

Другие вопросы по теме