今回はC#でLINQを使ってみます。
通常であれば、SQLデータベース、XMLドキュメントといった様々な種類のデータ毎にクエリ(データにアクセス、操作する手段)演算が必要ですが、
LINQを使うとそれを同じようなクエリ演算を使ってする事が出来ます。
LINQについてはC#リファレンスを参考にしました。
クエリを使う事が出来るのは、IEnumarable<T>かIQueryable<T> というインタフェースを実装しているクラスになります。
ListはIEnumerable
LINQを使う
LINQを使うには
1 2 3 | using System.Linq; |
usingディレクティブでSystem.Linqを指定します。
これでMonoDevelopのインテリセンス機能?でLINQのメソッドが現れるようになります。
実際にいくつかのLINQのメソッドを実行して確認してみます。
操作をする配列、List、Dictionaryのデータを用意し、これらの要素を操作してみます。
1 2 3 4 5 6 7 8 9 10 | string[] stringValue = new String[]{ "test1", "test2" }; List<int> list = new List<int> () { 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8 ,7, 6, 5, 4, 3, 2, 1}; Dictionary<string, int> dictionary = new Dictionary<string, int> () { {"key1", 0}, {"key2", 1}, {"key3", 2}, {"other", 3} }; |
それぞれ初期値を与えています。
条件が成立しているか判断するメソッド
Anyメソッド
Anyメソッドは条件に合う要素があるかどうかでbool値を返します。
ひとつでもあればtrueを返します。
1 2 3 | Debug.Log("stringValueにtestを含む要素があるか? " + stringValue.Any (x => x.Contains ("test"))); |
↑の場合はstringValueにはtest1、test2という要素があるのでtrueが返ります。
Allメソッド
Allメソッドは全ての要素が条件を満たしているかどうかを返します。
1 2 3 | Debug.Log("listの要素全てが0以上かどうか " + list.All (x => x >= 0)); |
↑の例ではtrueが表示されます。
条件にあった要素を抽出しIEnumerableインタフェースを返すメソッド
Whereメソッド
Whereメソッドは条件に合う要素からIEnumerableのジェネリックを返します。
1 2 3 | IEnumerable<int> tempList2 = list.Where (x => x >= 5); |
↑の例ではlistから5以上の要素でIEnumerableのジェネリックを返すので、foreach文等で要素を表示すると 5 6 7 8 9 9 8 7 6 5 が表示されます。
Whereメソッドは遅延実行なので、tempList2を使用する時にlist.Where(x => x >= 5)が実行されtempList2が決まる事になります。
遅延実行については後ほどやります。
Distinctメソッド
Distinctメソッドは要素の中から重複する値を持つ要素を削除してIEnumerableのインタフェースを返します。
1 2 3 | IEnumerable<int> enumerableList = list.Distinct (); |
foreach文等を使ってenumerableListを表示すると、1 2 3 4 5 6 7 8 9 という要素が表示されます。
Takeメソッド
Takeメソッドは要素の中から指定した数の要素を取得しIEnumerableジェネリックインタフェースを返します。
1 2 3 | IEnumerable<int> tempList3 = tempList2.Take (2); |
foreach文などでtempList3の要素を表示すると 1 2 が表示されます。
TakeWhileメソッド
TakeWhileメソッドを使用すると、要素が条件に合っている間要素を取得し、IEnumerableジェネリックインタフェースを返します。
1 2 3 | IEnumerable<int> tempList4 = list.TakeWhile (x => x <= 4); |
↑の例では要素が4以下の間要素を取りだしているので要素の値が5になった時点で終了します。
foreachでtempList4の要素を出力すると 1 2 3 4 が表示されます。
OrderByメソッド
OrderByメソッドを使うと要素を昇順に並べ替えをする事が出来ます。
1 2 3 4 5 6 | IEnumerable<int> orderList1 = list.OrderBy (x => x); foreach (var value in orderList1) { Debug.Log ("昇順に並べ替えたList " + value); } |
↑の例ではlistの要素を昇順に並べ替えています。
OrderByDescendingメソッド
OrderByDescedingメソッドを使用すると要素を降順に並べ替える事が出来ます。
1 2 3 4 5 6 | IEnumerable<int> orderList2 = list.OrderByDescending (x => x); foreach (var value in orderList2) { Debug.Log ("降順に並べ替えたList " + value); } |
OrderBy、OrderByDecendingで比較方法を指定
OrderByやOrderByDecendingの引数にICompareインタフェースを実装したクラスを渡すと、比較方法を変更する事が出来ます。
まずはICompareクラスを実装したクラスを作成し、比較メソッドであるCompareを作成します。
1 2 3 4 5 6 7 | class CompareClass : IComparer<int> { public int Compare(int x, int y) { return y - x; } } |
↑の例ではyからxを引くようにしているので要素の数を降順に並べ替えます。
実際に使う場合は
1 2 3 4 5 6 | IEnumerable<int> orderList3 = list.OrderBy (x => x, new CompareClass()); foreach (var value in orderList3) { Debug.Log ("比較クラスで降順並べ替えたList " + value); } |
listの要素の値をCompareClassのCompareで指定されている並びに変更しています。
listの要素が文字列で、文字列の長さで並べ替えをしたい時は
1 2 3 4 5 6 7 8 9 | List<string> stringList = new List<string> { "test1", "testt2", "testte3", "te", "tete" }; // リストの要素をCompareClassのCompareメソッドで比較して並べ替え IEnumerable<string> orderList3 = stringList.OrderBy (x => x.Length, new CompareClass()); foreach (var value in orderList3) { Debug.Log ("比較クラスで降順並べ替えしたstringList " + value); } |
要素を使った操作メソッド
Average
Averageメソッドは要素の平均値を求める事が出来ます。
1 2 3 | Debug.Log ("listの要素の平均値は " + list.Average ()); |
listリストの要素の平均値は5が表示されます。
Countメソッド
CountメソッドはListクラスのメソッドもあり要素数を返す事が出来ますが、LINQのCountメソッドを使うとPredicateデリゲートやラムダ式で条件にあった要素の数を返す事が出来ます。
1 2 3 | Debug.Log("listの要素で7以上の値の数は " + list.Count (x => x >= 7)); |
↑の例ではlistの要素で7以上の数値の要素を返すので6が表示されます。
条件に合う要素を抽出
Firstメソッド
Firstメソッドは要素の中から条件にあった最初の要素を取得しますが、要素がなかった場合は例外が発生します。
1 2 3 | Debug.Log("listから0以上の条件に合致した最初の要素 " + list.First (x => x >= 0)); |
↑の例では1が表示されます。
FirstOrDefaultメソッド
FirstOrDefaultメソッドは要素の中から条件にあった最初の要素を取得しますが、要素がなかった場合はその要素の型のデフォルト値が返ってきます。
1 2 3 | Debug.Log("listから0以上の条件に合致した最初の要素 " + list.FirstOrDefault (x => x >= 10)); |
↑の例では要素の中に10以上の値はないので、要素のint型のデフォルト値である0が返ってきます。
即時実行と遅延実行
WhereメソッドやDistinctメソッドではIEnumerableのジェネリックを値として取得出来ますが、Distinctの実行はその値を使う時にあらためてlistを取得し実行する遅延実行です。
その為Distinctメソッドの項目で作成したenumerableListを使った処理をする時に、list.Distinctが実行されenumerableListの値を作成します。
なので、enumerableListを作成した後に元のリストを変更した場合、その後のenumerableListも変更されます。
試しに以下のようなスクリプトを記述してみます。
1 2 3 4 5 6 7 8 9 10 | // listから重複する要素を取り除いたIEnumerableのリストを作成 IEnumerable<int> enumerableList = list.Distinct (); // 元のリストから3以上の値を削除する list.RemoveAll (x => x >= 3); // enumerableListの要素を出力 foreach (var item in enumerableList) { Debug.Log ("enumerableListValue " + item); } |
↑の例ではlistから重複する要素を取り除いた 1 2 3 4 5 6 7 8 9 という要素を持つenumerableListを作成します。
その後listから3以上の要素を削除します。
最後にenumerableListの要素を出力しますが、表示されるのは 1 2 となります。
単純に考えたらlistの中身を変更する前にenumerableListを作っているので 1 2 3 4 5 6 7 8 9 と表示されそうですが、Distinctは遅延実行なので、enumerableListを使う時にlist.Distinctを実行し、それがenumerableListに入ります。
遅延実行をしたくない時は、ToArray、ToList、ToDictionary等を使ってその場で配列やリスト、ディクショナリに変更します。
1 2 3 | List<int> tempList = list.Distinct ().ToList (); |
WhereやDistinct以外のメソッドでも遅延実行をするものがあるので、使う時に気を付けないといけません。
ToDictionaryでDictionaryを作成してみます。
1 2 3 4 5 6 | Dictionary<string, int> tempDic = dictionary.Where (dic => dic.Key.Contains ("key")).ToDictionary (dic => dic.Key, dic => dic.Value); foreach (var key in tempDic.Keys) { Debug.Log (key + " : " + tempDic [key]); } |
↑の例ではdictionaryのキーと値のペアがdicに渡され、そのキーにkeyという文字が含まれているものを抽出し、それをToDictionaryでDictionaryに変換しています。
ToDictionaryではキーと値をどれにするかを設定します。
結果は key1 0、 key2 1、 key3 2 が表示されます(otherにはkeyという文字列が含まれない為登録されません)。
チェーンメソッド
LINQはIEnumerableに対して行えるので、Whereメソッドの後にTake、ToList等、メソッドを繋げて実行する事も可能です。
1 2 3 4 5 6 | List<int> chainList = list.Where (x => x >= 2).Take (5).ToList (); foreach (var value in chainList) { Debug.Log ("チェーンメソッドを利用して作ったリスト " + value); } |
↑の例ではWhereの戻り値であるIEnumerable
結果として 2 3 4 5 6 が表示されます。
全サンプル
最後に全てのサンプルを搭載したスクリプトを掲載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | using UnityEngine; using System.Collections; using System.Linq; using System; using System.Collections.Generic; using System.Security.Policy; public class LINQTest : MonoBehaviour { // Use this for initialization void Start () { string[] stringValue = new String[]{ "test1", "test2" }; List<int> list = new List<int> () { 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8 ,7, 6, 5, 4, 3, 2, 1}; Dictionary<string, int> dictionary = new Dictionary<string, int> () { {"key1", 0}, {"key2", 1}, {"key3", 2}, {"other", 3} }; // stringValueにtestを含む要素があるかどうか Debug.Log("stringValueにtestを含む要素があるか? " + stringValue.Any (x => x.Contains ("test"))); // listの平均値を出力 Debug.Log ("listの要素の平均値は " + list.Average ()); // listの要素で7以上の数値の数を出力 Debug.Log("listの要素で7以上の値の数は " + list.Count (x => x >= 7)); // listの重複要素を出力 IEnumerable<int> enumerableList = list.Distinct (); // list.RemoveAll (x => x >= 3); foreach (var item in enumerableList) { Debug.Log ("enumerableListValue " + item); } List<int> tempList = list.Distinct ().ToList (); Debug.Log ("listの重複する要素を削除して作成したリストの要素"); foreach (var value in tempList) { Debug.Log (value); } // 条件にあった最初の要素を出力(なければ例外が発生) Debug.Log("listから0以上の条件に合致した最初の要素 " + list.First (x => x >= 0)); // 条件にあった最初の要素を出力(なければその型の規定値) Debug.Log("listから10以上の条件に合致した最初の要素 " + list.FirstOrDefault (x => x >= 10)); // listの要素全てが条件をクリアしているかどうか Debug.Log("listの要素全てが0以上かどうか " + list.All (x => x >= 0)); foreach (var item in list) { Debug.Log ("list " + item); } // 条件にあった要素からIEnumerableを取得 IEnumerable<int> tempList2 = list.Where (x => x >= 5); foreach (var value in tempList2) { Debug.Log ("listで5以上の要素を出力 " + value); } // listから要素を削除した後にtempList2を表示してみる list.RemoveAll (x => x >= 7); foreach (var value in tempList2) { Debug.Log ("listから7以上値を削除した後tempList2を表示 " + value); } // tempList2から2つの要素だけ取得 IEnumerable<int> tempList3 = list.Take (2); foreach (var value in tempList3) { Debug.Log ("listから2つの要素だけ取り出して作成したリストの出力 " + value); } // listから4以下の数値である間要素を取得 IEnumerable<int> tempList4 = list.TakeWhile (x => x <= 4); foreach (var value in tempList4) { Debug.Log ("listから4以下の値である間出力 " + value); } // dictionaryのキーで当てはまる条件のものを抽出し、Dictionaryに変換 Dictionary<string, int> tempDic = dictionary.Where (dic => dic.Key.Contains ("key")).ToDictionary (dic => dic.Key, dic => dic.Value); foreach (var key in tempDic.Keys) { Debug.Log (key + " : " + tempDic [key]); } List<int> chainList = list.Where (x => x >= 2).Take (5).ToList (); foreach (var value in chainList) { Debug.Log ("チェーンメソッドを利用して作ったリスト " + value); } // リストを昇順に並べ替え IEnumerable<int> orderList1 = list.OrderBy (x => x); foreach (var value in orderList1) { Debug.Log ("昇順に並べ替えたList " + value); } // リストを降順に並べ替え IEnumerable<int> orderList2 = list.OrderByDescending (x => x); foreach (var value in orderList2) { Debug.Log ("降順に並べ替えたList " + value); } List<string> stringList = new List<string> { "test1", "testt2", "testte3", "te", "tete" }; // リストの要素をCompareClassのCompareメソッドで比較して並べ替え IEnumerable<string> orderList3 = stringList.OrderBy (x => x.Length, new CompareClass()); foreach (var value in orderList3) { Debug.Log ("比較クラスで降順並べ替えたList " + value); } } } class CompareClass : IComparer<int> { public int Compare(int x, int y) { return y - x; } } |
実行結果は各自確認してみてください。(^_^;)
終わりに
LINQのメソッドは他にもありますが、ざっくりと紹介しました。
後で追記するかもしれません。