Есть ли хороший способ разбить коллекцию на части n с помощью LINQ?
Конечно, не обязательно равномерно.
То есть я хочу разделить коллекцию на подколлекции, каждая из которых содержит подмножество элементов, причем последняя коллекция может быть разорвана.
Как именно вы хотите, чтобы они разделились, если даже не (конечно, с учетом конца)?
кто связался с этим вопросом? Джон это ты? :-) вдруг на все эти ответы :-)
Связанный вопрос Разделить список на подсписки с LINQ
@Simon_Weaver Я попытался уточнить, о чем вы спрашиваете, на основе принятого ответа. Фактически, существует множество способов «разбить» список, включая разложение каждого элемента списка на его элементы и помещение их в так называемые «параллельные» списки.





Обновлено: Хорошо, похоже, я неправильно понял вопрос. Я прочитал это как «кусочки длины 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
По иронии судьбы, я вполне мог использовать его еще до вчерашнего дня. Но, написав этот ответ, он был бы лицемерным - и, глядя на обе версии, я думаю, что этот является немного легче читать с первого взгляда. Сначала я использовал вместо него список - Добавить еще удобнее :)
Ну, я просто добавил немного другой ответ (чтобы адресовать «n частей», а не «частей длины n») и смешал части вашей версии ;-p
конечно, на этот раз мне понадобилось 8 штук, а не 8 :-), и это отлично сработало
Спасибо, что не удалили, хотя это не ответ на OP, я хотел то же самое - кусочки длины n :).
@Jon, мне просто интересно, почему в этом случае вы предпочитаете ReadOnlyCollectionмассив только для чтения?
@Jon: Ах - хороший момент! Я имел в виду фиксированную длину массивов, но, конечно, в отличие от ReadOnlyCollection, элементы можно было заменить. Спасибо за ответ.
@Gishu - В самом деле - это именно то, что мне было нужно!
Проблема с этим подходом заключается в том, что он просматривает полный список источников (требующий, чтобы весь список был в памяти), прежде чем двигаться дальше. Кто-нибудь знает лучшее решение для больших перечислений?
@Dejan: Нет, это не так. Обратите внимание на использование yield return. Для этого требуется, чтобы в памяти находился один партия, но это все.
Да, я видел yield return, и, конечно, мой комментарий «необходимо просмотреть полный список» явно неверен. Простите. Произошло следующее: я пытался использовать метод в Parallel.ForEach, чтобы разделить мои данные, что привело к увеличению использования памяти. Я предполагаю, что TPL порождает слишком много задач, потому что yield return приходит только с после, заполняющим раздел, и поэтому на первом этапе будет просто подготовка разделов параллельно. Думаю, мне нужна правильная реализация msdn.microsoft.com/en-us/library/dd381768(VS.100).aspx.
@Dejan: Верно - я бы не хотел гадать, как он взаимодействует с параллельным секционированием LINQ, если честно :)
Буферизация - это недостаток, если у вас большие куски. Думаю, лучшее решение здесь (Йеппе Стиг Нильсен): stackoverflow.com/questions/13709626/…
@SalientBrain: Гм, решение Джеппе Стига Нильсена по-прежнему использует пакетную обработку - обратите внимание на вызов ToList в yield return GetNextBatch(enumerator).ToList();. Пакетная обработка - это нужно, иначе вы получите очень странные результаты, если пропустите один раздел.
@Jon Skeet: Ага, я это пропустил)) Так что никакой магии (. То же самое, другими словами. Спасибо.
Если порядок в этих частях не очень важен, вы можете попробовать следующее:
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
Чистый 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. Теперь это выглядит лучше.
Выполнение всех этих операций с модулями для длинных списков может оказаться немного дорогостоящим.
Было бы лучше использовать перегрузку Select, которая включает index.
Я добавил ответ, в котором используется синтаксис выбора перегрузки и цепочки методов.
@JonathanAllen См. Немодульный ответ stackoverflow.com/questions/438188/…
@HasanKhan: Что делать, если я не знаю, на сколько частей я его разбиваю. Я имею в виду, разбить на группы, скажем, по 3 человека.
Я не работаю, у меня есть только одна сплит-секция со счетом 1, и все мои предметы находятся внутри этой одной позиции, есть идеи?
@LucasRodriguesSena Этот метод просит вас нет. разделений, а не то, сколько вы хотите в каждом разделении. Вы можете получить отказ. разделений, разделив желаемый размер на общее количество пунктов.
.AsEnumerable() не требуется, IGrouping <T> уже является IEnumerable <T>.
@Alex Если вы возвращаете часть напрямую, возвращаемым типом будет IEnumerable <IGrouping <T>> вместо IEnumerable <IEnumerable <T>>
@HasanKhan Я не запускал код, но попробовал, и он отлично компилируется. Я думаю, поскольку возвращаемый тип явно IEnumerable<IEnumerable<T>>, при возврате части происходит неявное приведение. Нет?
@ Алекс, верно. Когда я писал это, в C# не было ковариации.
@HasanKhan, ничего страшного. Явность кода обычно - это хорошо.
Есть ли способ получить результат в виде IDictionary<int, IEnumerable<T>> вместо этого, используя модуль в качестве ключа?
Для 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())
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));
}
Объясните, пожалуйста, почему. Я без проблем пользуюсь этой функцией!
прочитайте вопрос еще раз и посмотрите, получите ли вы n (почти) частей одинаковой длины с вашей функцией
@Elmer, ваш ответ правильный, но вопрос неправильный. Ваш ответ дает неизвестное количество фрагментов с фиксированным размером для каждого фрагмента (точно так же, как Раздел, имя, которое вы ему дали). Но OP хочет функциональность Split, которая дает фиксированное количество фрагментов любого размера на фрагмент (надеюсь, равных или близких к одинаковым размерам). Возможно здесь больше подходит stackoverflow.com/questions/3773403/…
Я думаю, вы можете просто изменить i.Index / partitionSize на i.Index% partitionSize и получить запрошенный результат. Я также предпочитаю это принятому ответу, поскольку он более компактный и читаемый.
Я довольно часто использовал функцию разделения, которую опубликовал ранее. Единственное, что было плохо, это то, что он не транслировался полностью. Это не проблема, если вы работаете с несколькими элементами в своей последовательности. Мне понадобилось новое решение, когда я начал работать с более чем 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 Я думаю, что Reset () должен выбросить NotSupportedException, и все будет в порядке. Из документации MSDN: «Метод Reset предоставляется для взаимодействия с COM. Его не обязательно реализовывать; вместо этого разработчик может просто выбросить NotSupportedException».
@toong Вау, ты прав. Не знаю, как я это пропустил после всего этого времени.
Глючит! Я точно не помню, но (насколько я помню) он выполняет нежелательный шаг и может привести к уродливым побочным эффектам (например, с помощью datareader). Лучшее решение здесь (Джеппе Стиг Нильсен): stackoverflow.com/questions/13709626/…
Хорошо, я брошу шляпу на ринг. Преимущества моего алгоритма:
Код:
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 Согласно MSDN очистка LinkedList - это сложность O (N), поэтому это разрушит мою цель O (1). Конечно, вы можете утверждать, что foreach - это O (N) для начала ... :)
ваш ответ правильный, но вопрос неправильный. Ваш ответ дает неизвестное количество фрагментов с фиксированным размером для каждого фрагмента. Но OP хочет функциональность Split, которая дает фиксированное количество фрагментов любого размера на фрагмент (надеюсь, равных или близких к одинаковым размерам). Возможно здесь больше подходит stackoverflow.com/questions/3773403/…
@nawfal - согласен. Я подошел к этому так, потому что он не требует заранее знать длину перечислимого, что более эффективно для некоторых приложений. Если вам нужно фиксированное количество фрагментов, вы можете вызвать myEnum.Section (myEnum.Count () / number_of_chunks + 1). Я обновлю свой ответ, чтобы отразить это.
@Mike, зачем нужны связанные списки, отдельная статическая функция и т. д.? Почему бы не использовать простой подход, который, как мне кажется, делает функцию еще быстрее? В предыдущая ветка, которую я опубликовал, вот мой ответ, который представляет собой аналогичную реализацию, которая работает быстрее и без проблем со структурами сбора.
@nawfal - мне нужен был подход O (1) или «постоянное время». Skip () внутри вашего цикла делает ваш подход O (N), потому что время, необходимое для обработки пропуска, увеличивается линейно с количеством элементов. Рассмотрим перечислимое число с 1e12 элементами, разделенными на группы по два - чем дальше вы попадете в перечислимое, тем медленнее оно будет. Я также пытался избежать умножения, которое обходится дорого по сравнению с другими операциями.
@ Майк, ты проверил это? Надеюсь, вы знаете, что O (1) не означает быстрее, это означает только то, что время, необходимое для разделения, не масштабируется. Мне просто интересно, каково ваше обоснование слепо придерживаться O (1), когда оно может быть медленнее, чем другие O (n) для всех сценариев реальной жизни. Я даже проверил его на сумасшедший список прочности 10 ^ 8, и мой оказался еще быстрее. Надеюсь, вы знаете, что не существует даже стандартных типов коллекций, которые могут содержать 10 ^ 12 элементов ..
Несколько моментов: 1) почему вы продолжаете настаивать на том, что операции умножения / по модулю и т. д. Дороги? 2) myEnum.Section(myEnum.Count() / number_of_sections + 1) перечисляет первую коллекцию (и для данного вопроса может быть неэффективным), поэтому, пожалуйста, отредактируйте преимущества, которые вы упомянули для своего подхода.
3) Вы можете использовать облегченную структуру коллекции вместо LinkedList <T>, поскольку в ответах Джона Скита или Брэда они, как правило, работают быстрее. Как я уже сказал, O (1) не гарантирует скорости. Возможные накладные расходы с LinkedList перевешивают его выгоды (они предназначены для случайного удаления и вставки). Смотрите это ссылка здесь
@nawfal - Спасибо за подробный анализ, он меня держит в тонусе. Связанные списки в целом известны эффективными концевыми вставками, поэтому я выбрал их здесь. Однако я только что проверил его, и действительно, List <> намного быстрее. Я подозреваю, что это какая-то деталь реализации .NET, возможно, заслуживающая отдельного вопроса StackOverflow. Я изменил свой ответ, чтобы использовать List <> в соответствии с вашим предложением. Предварительное выделение емкости списка гарантирует, что конечная вставка по-прежнему будет O (1) и соответствует моей первоначальной цели дизайна. Я также перешел на встроенный .AsReadOnly () в .NET 4.5.
@nawfal - На уровне машинного кода mult / div / mod обычно намного дороже (на порядок), чем простые операции, такие как set / get / compare. Конечно, это зависит от процессора, компилятора и реализации, так что вам действительно стоит провести сравнительный анализ вашего конкретного случая, если эта операция секционирования создает узкое место.
Извините, это может быть глупый вопрос, но зачем нам здесь вызов .AsReadOnly ()?
Технически вам это не нужно, но это своего рода «лучшая практика», поскольку мы возвращаем IEnumerable <>, который подразумевает, что он неизменяемый.
Интересная ветка. Чтобы получить потоковую версию 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>.
Это все еще работает, если вы перечисляете только раздел, а не внутреннее перечисление? поскольку внутренний перечислитель отложен, ни один код для внутреннего (взять из текущего) не будет выполняться до тех пор, пока он не будет перечислен, поэтому movenext () будет вызываться только функцией внешнего раздела, верно? Если мои предположения верны, то это потенциально может привести к n разделам с n элементами в исходном перечислимом, а внутренние перечисления дадут неожиданные результаты.
@Brad, как и следовало ожидать, он "выйдет из строя", аналогично некоторым проблемам в этом потоке stackoverflow.com/questions/419019/… (в частности, stackoverflow.com/a/20953521/1037948)
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, так что это мой любимый ответ.
@ manu08, я пробовал ур код, у меня есть список var dept = {1,2,3,4,5}. После разделения результат будет похож на dept1 = {1,3,5} и dept2 = { 2,4 }, где parts = 2. Но результат, который мне нужен, это dept1 = {1,2,3} и dept2 = {4,5}.
У меня была такая же проблема с модулем, поэтому я рассчитал длину столбца с помощью int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);, а затем сделал деление с помощью .GroupBy(x => x.index / columnLength). Один из недостатков - Count () перечисляет список.
Этот метод (и ему подобные) полагается на то, что размер списка больше, чем количество частей, на которые он разбивается.
Это эффективно с точки зрения памяти и максимально откладывает выполнение (на пакет) и работает за линейное время 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/…! См. Комментарии.
Это мой способ: перечислять элементы и разбивать строки по столбцам.
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 ();
Вот небольшая настройка количества элементов вместо количества частей:
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;
}
}
С пометкой: вопрос не имеет ничего общего с asp.net. Пожалуйста, отметьте свои вопросы соответствующим образом.