Edited at

【C#】LINQ でコレクションをN個ずつの要素に分割する

More than 1 year has passed since last update.

配列の入れ子を平坦化するには LINQ のSelectManyを使いますが逆のメソッドがありません。

自分で作る必要がありますがLINQ のGroupByで簡単に行うことができます。

「コレクションをN個ずつの要素に分割する」ことをChunkというそうです。

Chunk…「かたまり」という意味

以下のコードは[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]に変換するコードです。

// 対象のコレクション

var list = Enumerable.Range(1, 10);

// N 個ずつの N
var chunkSize = 3;

var chunks = list.Select((v, i) => new { v, i })
.GroupBy(x => x.i / chunkSize)
.Select(g => g.Select(x => x.v));

// 動作確認
foreach (var chunk in chunks)
{
foreach (var item in chunk)
Console.Write($"{item} ");
Console.WriteLine();
}

1 2 3

4 5 6
7 8 9
10

よく使う場合には拡張メソッドとして定義しておくと便利そうです。

参考:[C#][VB] LINQでコレクションをチャンク(N個ずつ)に分割

using System.Linq;

using System.Collections.Generic;

public static class MyLinqExtentions
{
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunkSize)
{
if (chunkSize <= 0)
throw new ArgumentException("Chunk size must be greater than 0.", nameof(chunkSize));

return source.Select((v, i) => new { v, i })
.GroupBy(x => x.i / chunkSize)
.Select(g => g.Select(x => x.v));
}
}

yield returnを使ってもきれいな実装ができます。

    public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunkSize)

{
if (chunkSize <= 0)
throw new ArgumentException("Chunk size must be greater than 0.", nameof(chunkSize));

while (source.Any())
{
yield return source.Take(chunkSize);
source = source.Skip(chunkSize);
}
}

こんな感じで使えます。

通常のLINQと同様に遅延評価です。

var source = Enumerable.Range('a', 26).Select(n => (char)n);

var chunckSize = 7;
var chunks = source.Chunk(chunckSize);

// 動作確認
foreach (var chunk in chunks)
{
foreach (var item in chunk)
Console.Write($"{item} ");
Console.WriteLine();
}

a b c d e f g

h i j k l m n
o p q r s t u
v w x y z

文字列もIEnumrable<char>を実装しているので Chunk できます。

var source = "The quick brown fox jumps over the lazy dog";

var chunckSize = 7;
var chunks = source.Chunk(chunckSize);
// 動作確認
foreach (var chunk in chunks)
{
foreach (var item in chunk)
Console.Write($"{item} ");
Console.WriteLine();
}

T h e   q u i

c k b r o w
n f o x j
u m p s o v
e r t h e
l a z y d o
g

ただ、文字列の分割なら正規表現が柔軟かもしれません。

var text = "Cwm fjord veg balks nth pyx quiz";

var matches = Regex.Matches(text, @".{5}");
foreach (var match in matches)
Console.WriteLine(match.ToString());

Cwm f

jord
veg b
alks
nth p
yx qu