Как я могу разделить 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);
    }
}