(僕の周りで)あまり見かけないけど、使いこなせれば強力な LINQ メソッドの紹介です。
Zip
2つのシーケンスをインデックス順にマージして一つのシーケンスにします。
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var zipped = numbers.Zip(
words,
(n, w) => $"{n} {w}"
);
foreach (var item in zipped)
Console.WriteLine(item);
1 one
2 two
3 three
はみ出る要素は無視されます。
Union Intersect Except
Unionは2つのシーケンスの少なくともどちらかに含まれている要素(和集合)のシーケンスを返します。
string[] langs1 = { "C#", "Rust", "Elixir", "Swift", "Kotlin", };
string[] langs2 = { "Rust", "Clojure", "C#", "OCaml", };
var union = langs1.Union(langs2);
foreach (var lang in union)
Console.Write($"{lang} ");
C# Rust Elixir Swift Kotlin Clojure OCaml
Intersectは2つのシーケンスのどちらにも含まれる要素(積集合)のシーケンスを返します。
string[] langs1 = { "C#", "Rust", "Elixir", "Swift", "Kotlin", };
string[] langs2 = { "Rust", "Clojure", "C#", "OCaml", };
var intersect = langs1.Intersect(langs2);
foreach (var lang in intersect)
Console.Write($"{lang} ");
C# Rust
Exceptは対象のシーケンスからもう一方のシーケンスに属する要素を取り除いたシーケンスを返します。
string[] langs1 = { "C#", "Rust", "Elixir", "Swift", "Kotlin", };
string[] langs2 = { "Rust", "Clojure", "C#", "OCaml", };
var except = langs1.Except(langs2);
foreach (var lang in except)
Console.Write($"{lang} ");
Elixir Swift Kotlin
シーケンスの取り除かれる側と取り除く側を入れ替えると、結果も変わります。
string[] langs1 = { "C#", "Rust", "Elixir", "Swift", "Kotlin", };
string[] langs2 = { "Rust", "Clojure", "C#", "OCaml", };
var except = langs2.Except(langs1);
foreach (var lang in except)
Console.Write($"{lang} ");
Clojure OCaml
ToDictionary ToLookup
ToDictionaryはシーケンスや配列などをDictionaryに変換します。
Dictionary型に変換することでキーで高速に値にアクセスできます。
以下のようなBookクラスのリストがあるとします。
var books = new List<Book>
{
new Book {
ISBN = "ISBN● - AAAA - BBBB - C",
Title = "タイトル",
Author = "著者名",
PublishedYear = 2000
},
new Book { … },
new Book { … },
⋮
};
// ISBN をキー、値を Book オブジェクトとする Dictionary を作る
var bookDict = books.ToDictionary(b => b.ISBN);
// 値を指定するには、第2引数にラムダ式を与える
var bookDict = books.ToDictionary(b => b.ISBN, b => b.Title);
ToLookupはシーケンスや配列などをILookup型に変換します。
Dictionaryはキーに対し1つの値が割り当てられますが、
ILookUpはキーに対しコレクションが割り当てられます。
// 発行年をキーとし、Book オブジェクトのコレクションが値となる ILookup を作成する
var lookup = books.ToLookup(b => b.PublishedYear);
lookup[2000] // => 発行年が2000年の book オブジェクト一覧が返る
// 第二引数にラムダ式を与えることで、値を指定できる
var lookup = books.ToLookup(b => b.PublishedYear, b => b.Title);
ToLookupはGroupByとよく似ていますが、ToLookupは即時実行、GroupByは遅延評価です。
ToLookupならlookup[2000]のようにインデクサでアクセスできます。
値にリストを持つDictionaryを作る場合にはGroupByとToDictionaryを使います。
ToLookUpとToDcitinaryを組み合わせると2度ループが回ってしまいます。
// ToLookup と ToDictionary で2度ループが回る
var dic = books
.ToLookup(b => b.PublishedYear)
.ToDictionary(
group => group.Key,
group => group.ToList()
);
// GroupBy と ToDictionary なら ループが回るのは ToDictionary の一度だけ
var dic = books
.GroupBy(b => b.PublishedYear)
.ToDictionary(
group => group.Key,
group => group.ToList()
);
Distinct
メジャーかもしれませんが、僕の周りではあまり見ないので載せます。
シーケンスから重複した要素を削除したシーケンスを返します。
int[] ints = { 1, 2, 3, 4, 3, 2, 2, 5, 4 };
var distinct = ints.Distinct();
foreach (var n in distinct)
Console.Write($"{n} ");
1 2 3 4 5
GroupJoin
2つのシーケンスをグルーピングするGroupByと、
2つのシーケンスを結合するJoinを、同時にやってしまうメソッドです。
以下のような漫画雑誌と漫画のリストがあるとします。
var magazines = new[]
{
new { Name = "ジャンプ" },
new { Name = "サンデー"},
new { Name = "チャンピオン" },
};
var myComics = new[]
{
new { Title = "グラップラー刃牙", Publication = "チャンピオン" },
new { Title = "スラムダンク", Publication = "ジャンプ" },
new { Title = "HUNTER×HUNTER", Publication = "ジャンプ" },
new { Title = "名探偵コナン", Publication = "サンデー" }
};
上記2つのリストを結合し、漫画雑誌毎に漫画を振り分けます。
var groups = magazines.GroupJoin(
myComics,
magazine => magazine.Name,
comic => comic.Publication,
(magazine, comics) => new
{
Magazine = magazine.Name,
Comics = comics
});
foreach (var group in groups)
{
Console.WriteLine($"{group.Magazine}:");
foreach (var comic in group.Comics)
Console.WriteLine($" - {comic.Title}");
}
ジャンプ:
- スラムダンク
- HUNTER×HUNTER
サンデー:
- 名探偵コナン
チャンピオン:
- グラップラー刃牙
まとめ
LINQ には便利なメソッドがたくさんあります。
普段よく使うもの以外にもハマるとシーケンス・コレクション操作を強力にサポートしてくれるものがたくさんあるので、ぜひ使いこなしたいです。