こんな表を作った。(TablePressでJavaScriptのDataTablesライブラリというのを有効にしてるので、表を並べ替えたり値を検索してフィルタリングしたりできます。いじってみてください。)
料理 | 材料 |
---|---|
カレー | 肉 |
カレー | じゃがいも |
すき焼き | 肉 |
すき焼き | 卵 |
コロッケ | 肉 |
コロッケ | じゃがいも |
コロッケ | 卵 |
ポテトサラダ | じゃがいも |
ポテトサラダ | 卵 |
ポテトサラダに卵!? という人もいるかもしれないが、私はゆで卵をつぶして入れてるやつが好きだからいいのだ。それにマヨネーズには卵が入ってるのだし…。
今回は、この表から材料を指定して料理を選ぶというのをLINQを使ってやってみる。
準備
まず、データを用意する。
//料理とその材料をペアにして保持するクラス class Food { public string Dish; //料理 public string Stuff; //材料 } //データの配列 Food[] menu = new Food[] { new Food() { Dish ="カレー", Stuff="肉"}, new Food() { Dish ="カレー", Stuff="じゃがいも"}, new Food() { Dish = "すき焼き", Stuff = "肉" }, new Food() { Dish = "すき焼き", Stuff = "卵" }, new Food() { Dish = "コロッケ", Stuff = "肉" }, new Food() { Dish = "コロッケ", Stuff = "じゃがいも" }, new Food() { Dish = "コロッケ", Stuff = "卵" }, new Food() { Dish = "ポテトサラダ", Stuff = "じゃがいも" }, new Food() { Dish = "ポテトサラダ", Stuff = "卵" } };
材料を一つ指定して抽出
「肉を使った料理」を抽出する。
//肉を使った料理を抽出 IEnumerable<string> foods1 = menu .Where(x => x.Stuff == "肉") .Select(x => x.Dish); //抽出結果を表示 Console.WriteLine("肉を使った料理:"); foreach (string s in foods1) { Console.WriteLine(s); }
出力
肉を使った料理:
カレー
すき焼き
コロッケ
まぁ、ひとつの条件でできるのは当たり前だ。基本だし。
二つの材料をどちらも使っているのを抽出する(交わり)
では、二つの条件を同時に満たす場合を考えてみる。まずは単純に…
//肉とじゃがいもを使った料理【悪い例】 IEnumerable<string> foods2 = menu .Where(x => x.Stuff == "肉" && x.Stuff == "じゃがいも") .Select(x => x.Dish);
肉とじゃがいもを使った料理:
何も出てこない。表を見ればわかるが、材料列に肉とじゃがいもを両方書いている料理はない。このデータの場合、単純にWhere内で条件を追加しても「そんなデータはない」という結果になってしまう。ではどうするか?
肉を使った料理とじゃがいもを使った料理それぞれを抽出して、どちらにも名前の挙がった料理を選べばいい。つまり(肉料理)∩(じゃがいも料理)(集合の交わり)を出せばいいわけだ。
抽出結果の交わりを求める場合、IEnumerable<T>の拡張メソッド「Intersect」を使う。
//肉とじゃがいもを使った料理【良い例】 IEnumerable<string> foods2 = menu .Where(x => x.Stuff == "肉") .Select(x => x.Dish) .Intersect( menu .Where(x => x.Stuff == "じゃがいも") .Select(x => x.Dish));
肉とじゃがいもを使った料理:
カレー
コロッケ
「Intersect」メソッドはIEnumerable<T>なオブジェクトに付いてそれから列挙される要素と、引数で指定したIEnumerale<T>から列挙される要素との交わりを返す(戻り値もIEnumerable<T>)。
それぞれIEnumerable<T>なA、Bの交わりを出すには
A.Intersect(B)
と書く。(第2引数に、交わりを求める条件を指定するIEqualityComparerを取るオーバーロードもある。)
二つの材料のどちらかを使っているのを抽出する(結び)
両方使うのを抽出するのは、表に両方書いてある行がないんだから無理だったけど、どちらかを選ぶのなら大丈夫だろう、と思ったが…
//肉またはじゃがいもを使った料理【悪い例】 IEnumerable<string> foods3 = menu .Where(x => x.Stuff == "肉" || x.Stuff == "じゃがいも") .Select(x => x.Dish);
肉またはじゃがいもを使った料理:
カレー
カレー
すき焼き
コロッケ
コロッケ
ポテトサラダ
…結果にダブりが。どちらかの条件に合うものを単純に列挙していけば、そりゃそうなるわな…。
というわけで、この場合も集合を使う。今回は(肉料理)∪(ジャガイモ料理)(集合の結び)を取る。
結びを求める拡張メソッドは「Union」。
//肉またはじゃがいもを使った料理【良い例】 IEnumerable<string> foods3 = menu .Where(x => x.Stuff == "肉") .Select(x => x.Dish) .Union( menu .Where(x => x.Stuff == "じゃがいも") .Select(x => x.Dish));
肉またはじゃがいもを使った料理:
カレー
すき焼き
コロッケ
ポテトサラダ
Unionの構文もIntersectと同じ。
A.Union(B)
オーバーロードについても同じ。
ある材料は使っても別のある材料は使わないのを抽出する(差)
肉は使っても、じゃがいもを使わない料理を抽出したい…そんな場合は(肉料理)-(じゃがいも料理)(集合の差)を使う。拡張メソッドは「Except」。
//肉を使ってじゃがいもを使わない料理 IEnumerable<string> foods4 = menu .Where(x => x.Stuff == "肉") .Select(x => x.Dish) .Except( menu .Where(x => x.Stuff == "じゃがいも") .Select(x => x.Dish));
肉を使ってじゃがいもを使わない料理:
すき焼き
構文やオーバーロードは前二つと同じ。
A.Except(B)
条件を増やしたいとき
LINQの各メソッドはIEnumerable<T>を受けてIEnumerable<T>を返すようになっているので、いくつでも繋げられる。
//肉とじゃがいもと卵を使った料理 IEnumerable<string> foods5 = menu .Where(x => x.Stuff == "肉") .Select(x => x.Dish) .Intersect( menu .Where(x => x.Stuff == "じゃがいも") .Select(x => x.Dish)) .Intersect( menu .Where(x => x.Stuff == "卵") .Select(x => x.Dish));
肉とじゃがいもと卵を使った料理:
コロッケ
ということで、コロッケが最強となりました。(え
まとめ
A、B二つのクエリがあり、クエリ結果を組み合わせるとき
- AとBの交わり → A.Intersect(B)
- AとBの結び → A.Union(B)
- AからBを引いた差 → A.Except(B)
(ところで、コロ助がコロッケ好きなのはアニメ版だけの設定らしい…。)