Для тех, у кого есть проблемы с IE и другими.
{
float: left;
width: 1px;
height: 1px;
background-color: transparent;
border: none;
}
Попробуйте использовать следующий код.
public static IList<IList<T>> Split<T>(IList<T> source)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / 3)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
Идея состоит в том, чтобы сначала группировать элементы по индексам. Разделение на три приводит к группировке их в группы по 3. Затем преобразуйте каждую группу в список и IEnumerable
из List
в List
из List
s
Хорошо, вот мое занятие:
Пример использования
var src = new [] {1, 2, 3, 4, 5, 6};
var c3 = src.Chunks(3); // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.Chunks(4); // {{1, 2, 3, 4}, {5, 6}};
var sum = c3.Select(c => c.Sum()); // {6, 15}
var count = c3.Count(); // 2
var take2 = c3.Select(c => c.Take(2)); // {{1, 2}, {4, 5}}
Пояснения
Код работает путем вложенности два итератора на основе yield
.
Внешний итератор должен отслеживать, сколько элементов было эффективно использовано внутренним (куском) итератором. Это делается закрытием remaining
с помощью innerMoveNext()
. Неиспользуемые элементы куска отбрасываются до того, как следующий кусок будет получен внешним итератором. Это необходимо, потому что в противном случае вы получите несогласованные результаты, когда внутренние перечислимые элементы не будут (полностью) потреблены (например, c3.Count()
вернет 6).
Примечание: Ответ был обновлен для устранения недостатков, отмеченных @ aolszowka.
blockquote>
Вот процедура разбиения списка, которую я написал пару месяцев назад:
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;
}
Это старый вопрос, но это то, с чем я столкнулся; он перечисляет перечисление только один раз, но создает списки для каждого из разделов. Он не страдает от неожиданного поведения, когда 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;
}
}
}
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int chunkSize)
– krizzzn
24 June 2014 в 15:30
В общем, подход, предложенный CaseyB , отлично работает, на самом деле, если вы проходите в List<T>
, трудно его винить, возможно, я бы изменил его на:
public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
var pos = 0;
while (source.Skip(pos).Any())
{
yield return source.Skip(pos).Take(chunksize);
pos += chunksize;
}
}
Это позволит избежать массовых цепочек вызовов. Тем не менее, этот подход имеет общий недостаток. Он материализует два перечисления на кусок, чтобы подчеркнуть проблему:
foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
Console.WriteLine(item);
}
// wait forever
Чтобы преодолеть это, мы можем попробовать подход Кэмерона , который проходит вышеупомянутый тест в летающих цветах, поскольку он только перебирает перечисление один раз.
Проблема в том, что у него есть другой недостаток, он материализует каждый элемент в каждом фрагменте, проблема с этим подходом заключается в том, что вы работаете высоко в памяти.
Чтобы проиллюстрировать эту попытку:
foreach (var item in Enumerable.Range(1, int.MaxValue)
.Select(x => x + new string('x', 100000))
.Clump(10000).Skip(100).First())
{
Console.Write('.');
}
// OutOfMemoryException
Наконец, любая реализация должна иметь возможность обрабатывать итерацию кусков, например:
Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]
Многие очень оптимальные решения, такие как моя первая версия этого ответа, не удалось. Такую же проблему можно найти в answer.One оптимизированный ответ .
Чтобы решить все эти проблемы, вы можете использовать следующее:
namespace ChunkedEnumerator
{
public static class Extensions
{
class ChunkedEnumerable<T> : IEnumerable<T>
{
class ChildEnumerator : IEnumerator<T>
{
ChunkedEnumerable<T> parent;
int position;
bool done = false;
T current;
public ChildEnumerator(ChunkedEnumerable<T> parent)
{
this.parent = parent;
position = -1;
parent.wrapper.AddRef();
}
public T Current
{
get
{
if (position == -1 || done)
{
throw new InvalidOperationException();
}
return current;
}
}
public void Dispose()
{
if (!done)
{
done = true;
parent.wrapper.RemoveRef();
}
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
position++;
if (position + 1 > parent.chunkSize)
{
done = true;
}
if (!done)
{
done = !parent.wrapper.Get(position + parent.start, out current);
}
return !done;
}
public void Reset()
{
// per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
throw new NotSupportedException();
}
}
EnumeratorWrapper<T> wrapper;
int chunkSize;
int start;
public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
{
this.wrapper = wrapper;
this.chunkSize = chunkSize;
this.start = start;
}
public IEnumerator<T> GetEnumerator()
{
return new ChildEnumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class EnumeratorWrapper<T>
{
public EnumeratorWrapper (IEnumerable<T> source)
{
SourceEumerable = source;
}
IEnumerable<T> SourceEumerable {get; set;}
Enumeration currentEnumeration;
class Enumeration
{
public IEnumerator<T> Source { get; set; }
public int Position { get; set; }
public bool AtEnd { get; set; }
}
public bool Get(int pos, out T item)
{
if (currentEnumeration != null && currentEnumeration.Position > pos)
{
currentEnumeration.Source.Dispose();
currentEnumeration = null;
}
if (currentEnumeration == null)
{
currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
}
item = default(T);
if (currentEnumeration.AtEnd)
{
return false;
}
while(currentEnumeration.Position < pos)
{
currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
currentEnumeration.Position++;
if (currentEnumeration.AtEnd)
{
return false;
}
}
item = currentEnumeration.Source.Current;
return true;
}
int refs = 0;
// needed for dispose semantics
public void AddRef()
{
refs++;
}
public void RemoveRef()
{
refs--;
if (refs == 0 && currentEnumeration != null)
{
var copy = currentEnumeration;
currentEnumeration = null;
copy.Source.Dispose();
}
}
}
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
if (chunksize < 1) throw new InvalidOperationException();
var wrapper = new EnumeratorWrapper<T>(source);
int currentPos = 0;
T ignore;
try
{
wrapper.AddRef();
while (wrapper.Get(currentPos, out ignore))
{
yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
currentPos += chunksize;
}
}
finally
{
wrapper.RemoveRef();
}
}
}
class Program
{
static void Main(string[] args)
{
int i = 10;
foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
{
foreach (var n in group)
{
Console.Write(n);
Console.Write(" ");
}
Console.WriteLine();
if (i-- == 0) break;
}
var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();
foreach (var idx in new [] {3,2,1})
{
Console.Write("idx " + idx + " ");
foreach (var n in stuffs[idx])
{
Console.Write(n);
Console.Write(" ");
}
Console.WriteLine();
}
/*
10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
*/
Console.ReadKey();
}
}
}
Существует также раунд оптимизаций, которые вы могли бы ввести для нестандартной итерации кусков, которая здесь отсутствует.
Каким образом вы должны выбрать? Это полностью зависит от проблемы, которую вы пытаетесь решить. Если вы не заинтересованы в первом недостатке, простой ответ невероятно привлекателен.
Обратите внимание, что как и в большинстве методов, это небезопасно для многопоточности, материал может стать странным, если вы хотите сделать его потокобезопасным необходимо будет исправить EnumeratorWrapper
.
Несколько лет назад я написал метод расширения Clump. Отлично работает и является самой быстрой реализацией здесь. : P
/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
if (source == null)
throw new ArgumentNullException("source");
if (size < 1)
throw new ArgumentOutOfRangeException("size", "size must be greater than 0");
return ClumpIterator<T>(source, size);
}
private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
Debug.Assert(source != null, "source is null.");
T[] items = new T[size];
int count = 0;
foreach (var item in source)
{
items[count] = item;
count++;
if (count == size)
{
yield return items;
items = new T[size];
count = 0;
}
}
if (count > 0)
{
if (count == size)
yield return items;
else
{
T[] tempItems = new T[count];
Array.Copy(items, tempItems, count);
yield return tempItems;
}
}
}
Этот вопрос немного устарел, но я просто написал это, и я думаю, что он немного более изящный, чем другие предлагаемые решения:
/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
while (source.Any())
{
yield return source.Take(chunksize);
source = source.Skip(chunksize);
}
}
if (chunksize <= 0) throw new ArgumentException("Chunk size must be greater than zero.", "chunksize");
– mroach
1 March 2012 в 18:34
O(n²)
. Вы можете перебирать список и получать время O(n)
.
– hIpPy
30 August 2012 в 19:22
source
заменяется на обертку IEnumerable
каждый раз. Итак, взятие элементов из source
проходит через слои Skip
s
– Lasse Espeholt
24 November 2013 в 14:04
Мы можем улучшить решение @ JaredPar, чтобы сделать истинную ленивую оценку. Мы используем метод GroupAdjacentBy
, который дает группы последовательных элементов с одним и тем же ключом:
sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))
Поскольку группы получаются один за другим, это решение работает эффективно с длинными или бесконечными последовательностями.
Вы могли использовать несколько запросов, которые используют Take
и Skip
, но это добавит слишком много итераций на Исходный список, я считаю.
Скорее, я думаю, что вы должны создать собственный итератор, например:
public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
Затем вы можете вызвать это, и LINQ включен так, вы можете выполнять другие операции над результирующими последовательностями.
В ответ на ответ Сэма , я чувствовал, что существует более простой способ сделать это без:
Итак, вот еще один проход, который я кодировал в методе расширения до IEnumerable<T>
Chunk
:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException("source");
if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
Ничего удивительного там, просто базовая проверка ошибок.
Переход к ChunkInternal
:
private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
this IEnumerable<T> source, int chunkSize)
{
// Validate parameters.
Debug.Assert(source != null);
Debug.Assert(chunkSize > 0);
// Get the enumerator. Dispose of when done.
using (IEnumerator<T> enumerator = source.GetEnumerator())
do
{
// Move to the next element. If there's nothing left
// then get out.
if (!enumerator.MoveNext()) yield break;
// Return the chunked sequence.
yield return ChunkSequence(enumerator, chunkSize);
} while (true);
}
В принципе , он получает IEnumerator<T>
и вручную выполняет итерацию через каждый элемент. Он проверяет, есть ли какие-либо элементы, которые в настоящее время перечислены. После того, как каждый фрагмент перечислит, если не осталось никаких элементов, он выйдет из строя.
Как только он обнаруживает, что есть элементы в последовательности, он делегирует ответственность за внутреннюю реализацию IEnumerable<T>
до ChunkSequence
:
private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator,
int chunkSize)
{
// Validate parameters.
Debug.Assert(enumerator != null);
Debug.Assert(chunkSize > 0);
// The count.
int count = 0;
// There is at least one item. Yield and then continue.
do
{
// Yield the item.
yield return enumerator.Current;
} while (++count < chunkSize && enumerator.MoveNext());
}
Поскольку MoveNext
уже вызывается на IEnumerator<T>
, переданном в ChunkSequence
, он возвращает элемент, возвращенный Current
, а затем увеличивает счетчик, чтобы никогда не возвращать больше, чем chunkSize
элементов и переходить к следующему элементу в последовательности после каждой итерации (но закорочен, если количество полученных элементов превышает размер куска).
Если никаких элементов не осталось, то метод InternalChunk
сделает другой проход во внешнем цикле, но когда MoveNext
вызывается второй раз, он все равно вернет false, в качестве в документации (выделенное мое):
Если MoveNext передает конец коллекции, перечислитель помещается после последнего элемента в коллекции, а MoveNext возвращает false. Когда перечислитель находится в этой позиции, последующие вызовы MoveNext также возвращают false до тех пор, пока не вызывается Reset.
В этот момент цикл прерывается, и последовательность последовательностей прекращается.
Это простой тест:
static void Main() { string s = "agewpsqfxyimc"; int count = 0; // Group by three. foreach (IEnumerable<char> g in s.Chunk(3)) { // Print out the group. Console.Write("Group: {0} - ", ++count); // Print the items. foreach (char c in g) { // Print the item. Console.Write(c + ", "); } // Finish the line. Console.WriteLine(); } }
Выход:
Group: 1 - a, g, e, Group: 2 - w, p, s, Group: 3 - q, f, x, Group: 4 - y, i, m, Group: 5 - c,
Важно отметить, что не работает, если вы не сливайте всю последовательность потомков или не разбивайте их в любой точке родительской последовательности. Это важный аспект, но если ваш случай использования заключается в том, что вы будете потреблять каждый элемент последовательности последовательностей, то это будет работать для вас.
Кроме того, он будет делать странные вещи, если вы играете с порядком, так же, как Сэм делал в какой-то момент .
List<T>
, у вас, очевидно, будут проблемы с памятью из-за буферизации. Оглядываясь назад, я должен был отметить это в ответе, но, похоже, в то время основное внимание уделялось слишком много итераций. Тем не менее, ваше решение действительно волосатое. Я не тестировал его, но теперь мне интересно, есть ли менее волосатое решение.
– casperOne
3 May 2012 в 13:05
System.Interactive предоставляет Buffer()
для этой цели. Некоторые быстрые тесты показывают, что производительность похожа на решение Сэма.
Buffer()
возвращает IEnumerable<IList<T>>
, так что да, у вас, вероятно, возникнет проблема - он не течет, как ваш.
– dahlbyk
3 May 2012 в 20:53
Если список имеет тип system.collections.generic, вы можете использовать метод CopyTo для копирования элементов вашего массива в другие вспомогательные массивы. Вы указываете начальный элемент и количество копируемых элементов.
Вы также можете сделать 3 клона исходного списка и использовать «RemoveRange» в каждом списке, чтобы уменьшить список до нужного размера.
Или просто создайте вспомогательный метод, чтобы сделать это для вас.
Для всех, кто интересуется решением с пакетом / поддержкой, библиотека MoreLINQ предоставляет метод расширения Batch
, который соответствует вашему запросу:
IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);
Реализация Batch
аналогична ответу Кэмерона МакФарланда с добавлением перегрузки для преобразования пакета / партии перед возвратом и выполняется достаточно хорошо.
Настолько, что это подход Сэм Шаффрон .
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");
return BatchImpl(source, size).TakeWhile(x => x.Any());
}
static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
var values = new List<T>();
var group = 1;
var disposed = false;
var e = source.GetEnumerator();
try
{
while (!disposed)
{
yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
group++;
}
}
finally
{
if (!disposed)
e.Dispose();
}
}
static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
var min = (group - 1) * size + 1;
var max = group * size;
var hasValue = false;
while (values.Count < min && e.MoveNext())
{
values.Add(e.Current);
}
for (var i = min; i <= max; i++)
{
if (i <= values.Count)
{
hasValue = true;
}
else if (hasValue = e.MoveNext())
{
values.Add(e.Current);
}
else
{
dispose();
}
if (hasValue)
yield return values[i - 1];
else
yield break;
}
}
}
Это следующее решение является наиболее компактным, я мог бы придумать, что это 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;
}
}
Мы обнаружили, что решение Дэвида Б работает лучше всего. Но мы адаптировали его к более общему решению:
list.GroupBy(item => item.SomeProperty)
.Select(group => new List<T>(group))
.ToArray();
Использование модульного разбиения:
public IEnumerable<IEnumerable<string>> Split(IEnumerable<string> input, int chunkSize)
{
var chunks = (int)Math.Ceiling((double)input.Count() / (double)chunkSize);
return Enumerable.Range(0, chunks).Select(id => input.Where(s => s.GetHashCode() % chunks == id));
}
Чтобы вставить мои два цента ...
Используя тип списка для источника, который должен быть помечен, я нашел еще одно очень компактное решение:
public static IEnumerable<IEnumerable<TSource>> Chunk<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
// copy the source into a list
var chunkList = source.ToList();
// return chunks of 'chunkSize' items
while (chunkList.Count > chunkSize)
{
yield return chunkList.GetRange(0, chunkSize);
chunkList.RemoveRange(0, chunkSize);
}
// return the rest
yield return chunkList;
}
Может работать с бесконечными генераторами:
a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
.Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
.Where((x, i) => i % 3 == 0)
Демо-код: https://ideone.com/GKmL7M
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
private static void DoIt(IEnumerable<int> a)
{
Console.WriteLine(String.Join(" ", a));
foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
Console.WriteLine(String.Join(" ", x));
Console.WriteLine();
}
public static void Main()
{
DoIt(new int[] {1});
DoIt(new int[] {1, 2});
DoIt(new int[] {1, 2, 3});
DoIt(new int[] {1, 2, 3, 4});
DoIt(new int[] {1, 2, 3, 4, 5});
DoIt(new int[] {1, 2, 3, 4, 5, 6});
}
}
1
1 2
1 2 3
1 2 3
1 2 3 4
1 2 3
1 2 3 4 5
1 2 3
1 2 3 4 5 6
1 2 3
4 5 6
Но на самом деле я бы предпочел написать соответствующий метод без linq.
Старый код, но это то, что я использовал:
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;
}
}
Как насчет этого?
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 () является линейным по количеству принятых элементов. Так что это должно хорошо работать.
полностью ленив, нет счета или копирования:
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
{
if (len == 0)
throw new ArgumentNullException();
var enumer = source.GetEnumerator();
while (enumer.MoveNext())
{
yield return Take(enumer.Current, enumer, len);
}
}
private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
{
while (true)
{
yield return head;
if (--len == 0)
break;
if (tail.MoveNext())
head = tail.Current;
else
break;
}
}
}
Просто вставляем мои два цента. Если вы хотите «ведро» списка (визуализировать слева направо), вы можете сделать следующее:
public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
{
List<List<T>> result = new List<List<T>>();
for (int i = 0; i < numberOfBuckets; i++)
{
result.Add(new List<T>());
}
int count = 0;
while (count < source.Count())
{
var mod = count % numberOfBuckets;
result[mod].Add(source[count]);
count++;
}
return result;
}
Я считаю, что этот небольшой фрагмент делает работу довольно хорошо.
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;
}
}
Я взял основной ответ и сделал его контейнером МОК, чтобы определить, где разделить. ( Для тех, кто действительно ищет только разделение на 3 элемента, при чтении этого сообщения при поиске ответа? )
Этот метод позволяет разделить на любой тип элемента как
public static List<List<T>> SplitOn<T>(List<T> main, Func<T, bool> splitOn)
{
int groupIndex = 0;
return main.Select( item => new
{
Group = (splitOn.Invoke(item) ? ++groupIndex : groupIndex),
Value = item
})
.GroupBy( it2 => it2.Group)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
Итак, для OP код будет
var it = new List<string>()
{ "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
int index = 0;
var result = SplitOn(it, (itm) => (index++ % 3) == 0 );
[a,g,e]
, прежде чем перечислять больше исходного списка. – Colonel Panic 11 July 2012 в 15:20GroupBy(x=>f(x)).First()
никогда не даст группе. OP спросил о списках, но если мы напишем для работы с IEnumerable, делая только одну итерацию, мы пожинаем преимущество производительности. – Colonel Panic 11 July 2012 в 23:16