Я пытаюсь разделить список на несколько небольших списков.
Моя проблема: Моя функция разбивать списки не разбивает их на списки правильного размера. Он должен разбить их на списки размером 30, но вместо этого он разбивает их на списки размером 114?
Как сделать так, чтобы моя функция разбивала список на X списков размером 30 или меньше ?
public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30)
{
List<List<float[]>> list = new List<List<float[]>>();
for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
List <float[]> subLocat = new List <float[]>(locations);
if (subLocat.Count >= ((i*nSize)+nSize))
subLocat.RemoveRange(i*nSize, nSize);
else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));
Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
list.Add (subLocat);
}
return list;
}
Если я использую функцию в списке размером 144, то вывод:
Индекс: 4, Размер: 120
Индекс: 3, Размер: 114
Индекс: 2, Размер: 114
Индекс: 1, Размер: 114
Индекс: 0, Размер: 114
Ответы:
Универсальная версия:
источник
GetRange(3, 3)
Я бы предложил использовать этот метод расширения для разбиения списка источников на подсписки с указанным размером фрагмента:
Например, если вы разбиваете список из 18 элементов на 5 элементов на блок, он дает вам список из 4 подсписков со следующими элементами внутри: 5-5-5-3.
источник
ToList()
s, и пусть ленивая оценка сделает это волшебством.как насчет:
источник
ToList
но я не стал бы пытаться оптимизировать ее - она настолько тривиальна и вряд ли является узким местом. Основным преимуществом этой реализации является ее тривиальность, которую легко понять. Если вы хотите, вы можете использовать принятый ответ, он не создает эти списки, но немного сложнее..Skip(n)
перебираетn
элементы каждый раз, когда он вызывается, хотя это может быть нормально, это важно учитывать для кода, критичного к производительности. stackoverflow.com/questions/20002975/….Skip()
s в кодовой базе моей компании, и, хотя они не могут быть «оптимальными», они работают просто отлично. Такие вещи, как операции с БД, в любом случае занимают гораздо больше времени. Но я думаю, что важно отметить, что.Skip()
«касается» каждого элемента <n на своем пути, вместо того, чтобы переходить непосредственно к n-му элементу (как вы могли ожидать). Если у вашего итератора есть побочные эффекты от прикосновения к элементу, это.Skip()
может быть причиной труднодоступных ошибок.Решение Serj-Tm отлично, также это общая версия как метод расширения для списков (поместите его в статический класс):
источник
Я считаю принятый ответ (Serj-Tm) наиболее надежным, но я хотел бы предложить общую версию.
источник
В библиотеке MoreLinq есть метод с именем
Batch
Результат
ids
разделены на 5 кусков с 2 элементами.источник
У меня есть универсальный метод, который принимает любые типы, включая float, и он был протестирован модулем, надеюсь, он поможет:
источник
values.Count()
вызовет полное перечисление и потомvalues.ToList()
другое. Безопаснее сделать,values = values.ToList()
это уже материализовано.Хотя многие из приведенных выше ответов выполняют свою работу, все они ужасно терпят неудачу в бесконечной последовательности (или действительно длинной последовательности). Следующее является полностью онлайновой реализацией, которая гарантирует лучшее время и возможную сложность памяти. Мы только один раз повторяем перечисляемый источник и используем ленивый возврат для ленивых вычислений. Потребитель может выбрасывать список на каждой итерации, делая объем памяти равным
batchSize
количеству элементов в списке .РЕДАКТИРОВАТЬ: Просто сейчас реализация ОП просит разбить
List<T>
на более мелкиеList<T>
, поэтому мои комментарии относительно бесконечных перечислимых не применимы к ОП, но могут помочь другим, кто в конечном итоге здесь. Эти комментарии были в ответ на другие опубликованные решения, которые используютIEnumerable<T>
в качестве входных данных для своих функций, но перечисляют источник, перечисляемый несколько раз.источник
IEnumerable<IEnumerable<T>>
версия лучше, так как в ней не так многоList
конструкций.IEnumerable<IEnumerable<T>>
заключается в том, что реализация, вероятно, будет полагаться на потребителя, полностью перечисляющего каждый внутренний перечислимый получаемый результат. Я уверен, что решение может быть сформулировано таким образом, чтобы избежать этой проблемы, но я думаю, что полученный код может довольно быстро усложниться. Кроме того, поскольку он ленив, мы генерируем только один список за раз, и распределение памяти происходит ровно один раз для каждого списка, так как мы заранее знаем размер.Дополнение после очень полезного комментария mhand в конце
Оригинальный ответ
Хотя большинство решений могут работать, я думаю, что они не очень эффективны. Предположим, если вы хотите только первые несколько элементов из первых нескольких кусков. Тогда вы не захотите перебирать все (zillion) элементы в вашей последовательности.
Следующее будет, в крайнем случае, перечисляться дважды: один раз для дубля и один раз для пропуска. Он не будет перечислять больше элементов, чем вы будете использовать:
Сколько раз это будет Перечислять последовательность?
Предположим, вы делите свой источник на куски
chunkSize
. Вы перечисляете только первые N кусков. Из каждого перечисленного фрагмента вы будете перечислять только первые М элементов.Any получит Enumerator, выполнит 1 MoveNext () и вернет возвращенное значение после удаления Enumerator. Это будет сделано N раз
Согласно справочному источнику это будет делать что-то вроде:
Это не делает ничего, пока вы не начнете перечислять по извлеченному чанку. Если вы получаете несколько чанков, но решаете не перечислять первый чанк, то foreach не выполняется, как вам покажет ваш отладчик.
Если вы решите взять первые M элементов первого блока, то возврат доходности будет выполнен ровно M раз. Это означает:
После возврата первого чанка мы пропускаем этот первый чанк:
Еще раз: мы посмотрим на справочный источник, чтобы найти
skipiterator
Как видите,
SkipIterator
вызовы выполняютсяMoveNext()
один раз для каждого элемента в чанке. Это не звонитCurrent
.Итак, для каждого чанка мы видим, что сделано следующее:
Take ():
Если содержимое перечисляется: GetEnumerator (), один MoveNext и один Current для каждого перечисляемого элемента, Dispose enumerator;
Skip (): для каждого перечисляемого чанка (НЕ для содержимого чанка): GetEnumerator (), MoveNext () chunkSize times, без Current! Распорядиться перечислителем
Если вы посмотрите на то, что происходит с перечислителем, вы увидите, что есть много вызовов MoveNext (), и только вызовы
Current
для тех элементов TSource, к которым вы фактически решаете обратиться.Если вы возьмете N Chunks размером chunkSize, то вызовите MoveNext ()
Если вы решили перечислить только первые M элементов каждого извлеченного фрагмента, то вам нужно вызывать MoveNext M раз для каждого перечисленного блока.
Общая
Так что если вы решили перечислить все элементы всех кусков:
Будет ли MoveNext много работать или нет, зависит от типа исходной последовательности. Для списков и массивов это простое увеличение индекса, возможно, с проверкой вне диапазона.
Но если ваш IEnumerable является результатом запроса к базе данных, убедитесь, что данные действительно материализованы на вашем компьютере, в противном случае данные будут выбираться несколько раз. DbContext и Dapper правильно передадут данные в локальный процесс, прежде чем они будут доступны. Если вы перечислите одну и ту же последовательность несколько раз, она не будет выбрана несколько раз. Dapper возвращает объект List, DbContext запоминает, что данные уже получены.
От вашего репозитория зависит, целесообразно ли вызывать AsEnumerable () или ToLists (), прежде чем вы начнете делить элементы на куски
источник
2*chunkSize
? Это смертельно опасно в зависимости от источника перечисляемого (возможно, БД или другого незарегистрированного источника). Представьте это перечисляемое в качестве входных данныхEnumerable.Range(0, 10000).Select(i => DateTime.UtcNow)
- вы будете получать разное время при каждом перечислении перечислимого, поскольку оно не запоминаетсяEnumerable.Range(0, 10).Select(i => DateTime.UtcNow)
. При вызовеAny
вы будете пересчитывать текущее время каждый раз. Не так уж и плохоDateTime.UtcNow
, но рассмотрим перечисляемый, поддерживаемый курсором соединения с базой данных / sql или подобным. Я видел случаи, когда былиисточник
источник
Как насчет этого? Идея заключалась в том, чтобы использовать только один цикл. И, кто знает, может быть, вы используете только реализации IList для своего кода, и вы не хотите приводить их к List.
источник
Еще один
источник
источник
источник
Я столкнулся с этой же потребностью и использовал комбинацию методов Linq Skip () и Take () . Я умножаю число, которое я беру, на количество итераций, и это дает мне количество пропущенных элементов, затем я беру следующую группу.
источник
На основании Дмитрия Павлова я бы снял
.ToList()
. А также избегайте анонимного класса. Вместо этого мне нравится использовать структуру, которая не требует выделения памяти в куче. (АValueTuple
также сделал бы работу.)Это можно использовать подобно следующему, который выполняет итерацию по коллекции только один раз, а также не выделяет значительную память.
Если конкретный список действительно необходим, я бы сделал это так:
источник