Как я могу разделить IEnumerable <String> на группы IEnumerable <string>
У меня есть IEnumerable<string
> , который я хотел бы разделить на группы по три, поэтому, если бы у моего ввода было 6 элементов, я бы получил возвращаемый IEnumerable<IEnumerable<string>>
с двумя элементами, каждый из которых содержал бы IEnumerable<string>
, который моя строка содержимое в нем.
Я ищу, как это сделать с Linq, а не просто для цикла
Спасибо
Ответы
Ответ 1
var result = sequence.Select((s, i) => new { Value = s, Index = i })
.GroupBy(item => item.Index / 3, item => item.Value);
Обратите внимание, что это вернет IEnumerable<IGrouping<int,string>>
, который будет функционально подобен тому, что вы хотите. Однако, если вам строго нужно набрать его как IEnumerable<IEnumerable<string>>
(перейти к методу, который ожидает его в С# 3.0, который не поддерживает дисперсию генериков), вы должны использовать Enumerable.Cast
:
var result = sequence.Select((s, i) => new { Value = s, Index = i })
.GroupBy(item => item.Index / 3, item => item.Value)
.Cast<IEnumerable<string>>();
Ответ 2
Это поздний ответ на этот поток, но вот метод, который не использует временное хранилище:
public static class EnumerableExt
{
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> input, int blockSize)
{
var enumerator = input.GetEnumerator();
while (enumerator.MoveNext())
{
yield return nextPartition(enumerator, blockSize);
}
}
private static IEnumerable<T> nextPartition<T>(IEnumerator<T> enumerator, int blockSize)
{
do
{
yield return enumerator.Current;
}
while (--blockSize > 0 && enumerator.MoveNext());
}
}
И некоторый тестовый код:
class Program
{
static void Main(string[] args)
{
var someNumbers = Enumerable.Range(0, 10000);
foreach (var block in someNumbers.Partition(100))
{
Console.WriteLine("\nStart of block.");
foreach (int number in block)
{
Console.Write(number);
Console.Write(" ");
}
}
Console.WriteLine("\nDone.");
Console.ReadLine();
}
}
Ответ 3
Я знаю, что это уже ответили, но если вы планируете часто принимать фрагменты IEnumerables, я рекомендую сделать общий метод расширения следующим образом:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkSize)
{
return source.Where((x,i) => i % chunkSize == 0).Select((x,i) => source.Skip(i * chunkSize).Take(chunkSize));
}
Затем вы можете использовать sequence.Split(3)
, чтобы получить то, что хотите.
(вы можете называть его чем-то другим, например "slice" или "chunk", если вам не нравится, что "split" уже определен для строк. "Split" - это то, что я назвал моим.)
Ответ 4
Вдохновленный по внедрению @dicegiuy30, я хотел создать версию, которая только итерации по источнику один раз и не создает весь результирующий набор в памяти для компенсации. Лучше всего я придумал следующее:
public static IEnumerable<IEnumerable<T>> Split2<T>(this IEnumerable<T> source, int chunkSize) {
var chunk = new List<T>(chunkSize);
foreach(var x in source) {
chunk.Add(x);
if(chunk.Count <= chunkSize) {
continue;
}
yield return chunk;
chunk = new List<T>(chunkSize);
}
if(chunk.Any()) {
yield return chunk;
}
}
Таким образом я строю каждый кусок по требованию. Мне жаль, что я не должен избегать List<T>
, а также просто потопить это, но еще не понял этого.
Ответ 5
с помощью Microsoft.Reactive вы можете сделать это довольно просто, и вы будете перебирать только один раз через источник.
IEnumerable<string> source = new List<string>{"1", "2", "3", "4", "5", "6"};
IEnumerable<IEnumerable<string>> splited = source.ToObservable().Buffer(3).ToEnumerable();
Ответ 6
Мы можем улучшить решение @Afshari, чтобы сделать истинную ленивую оценку. Мы используем метод GroupAdjacentBy
, который дает группы последовательных элементов с одним и тем же ключом:
sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))
Поскольку группы получаются один за другим, это решение эффективно работает с длинными или бесконечными последовательностями.
Ответ 7
Я придумал другой подход. Он использует итератор while
в порядке, но результаты кэшируются в памяти, как обычный LINQ, до тех пор, пока это не понадобится.
Вот код.
public IEnumerable<IEnumerable<T>> Paginate<T>(this IEnumerable<T> source, int pageSize)
{
List<IEnumerable<T>> pages = new List<IEnumerable<T>>();
int skipCount = 0;
while (skipCount * pageSize < source.Count) {
pages.Add(source.Skip(skipCount * pageSize).Take(pageSize));
skipCount += 1;
}
return pages;
}
Ответ 8
Ответ Mehrdad Afshari превосходный. Вот метод расширения, который инкапсулирует его:
using System.Collections.Generic;
using System.Linq;
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> GroupsOf<T>(this IEnumerable<T> enumerable, int size)
{
return enumerable.Select((v, i) => new {v, i}).GroupBy(x => x.i/size, x => x.v);
}
}