TJSでLINQ to Objectsが使えるようになるライブラリlinq.tjsを公開しました。
https://github.com/sakano/linq.tjs
LINQ to ObjectsはC#の強力なコレクション操作ライブラリです。foreachすらないtjsではfor文を回すしかありませんでしたが、linq.tjsではより簡潔に複雑な処理を記述出来ます。
吉里吉里2/Zどちらでも使えます。
使用方法
- releaseページからSource codeをダウンロードします。
- linqフォルダに入っているlinq.tjs, linq_utility.tjs, linq__order.tjs, linq_generate.tjsを自分のプロジェクトフォルダに入れます。
- 最新のScriptsEx.dllをダウンロードして吉里吉里と同じフォルダに入れます。
- 以下のコードのようにlinq.tjsを読み込みます。
Scripts.execStorage("linq.tjs");
使用例
例えば以下のように使えます。
Enumerable.range(1, 10) .where(function(x) { return x % 2 == 0;}) .select(function(x) { return x * 2; }) .forEach(function(x) { System.inform(x); }); // 4,8,12,16,20 が表示される
使い方はC#のLINQと全く同じです。簡単に説明すると次のように動作します。
1. Enumerable.range(1, 10)で1~10の整数を生成
2. .where(function(x) { return x % 2 == 0;}) で偶数のみ抽出
3. .select(function(x) { return x * 2; }) で抽出した偶数を2倍
4. .forEach(function(x) { System.inform(x); }); でダイアログを表示
文字列関数
function(x) { return ~; }が鬱陶しいのでlinq.jsを見習って文字列で指定できるようにしています。
“XXX => YYY”を”function(XXX) { return YYY; }”に変換するだけですが結構便利です。先の例は以下のようになります。
Enumerable.range(1, 10) .where("x => x % 2 == 0") .select("x => x * 2") .forEach("x => System.inform(x);"); // =>,4,8,12,16,20
さらに省略してYYY部分のみでも使えます。この場合は第1引数が_、第2引数が_2、第3引数が_3、…のようになります。つまり”function(_, _2, _3, _4, _5, _6, _7, _8, _9) { return YYY; }”に変換されます。
Enumerable.range(1, 10) .where("_ % 2 == 0") .select("_ * 2") .forEach("System.inform(_);"); // =>,4,8,12,16,20
引数の受け渡し
tjsにはjavascriptと異なりレキシカルスコープがありません。つまりfunctionの中で外にある変数が参照できずLINQを使う上で非常に不便です。なのでselectやwhereなどコールバックを引数にとる関数では、通常の引数の後ろに渡された引数をそのままコールバックに渡すようにしています。
以下の例ではargやcaptionとして引数を受け取っています。indexはここでは使っていませんが現在のインデックス番号です。
Enumerable.range(1, 10) .where("x, index, arg => x % arg == 0", 2) .select("x, index, arg => x * arg", 2) .forEach("x, index, caption => System.inform(x, caption);", "結果表示"); // =>,4,8,12,16,20
Enumerable.from
Enumerable.fromに配列、辞書配列、文字列を渡すとLINQが使えるようになります。
配列の場合はそれぞれの要素が順番に列挙されるだけです。また、toArray()を使うと列挙中のシーケンスを配列に変換できます。
var array = Enumerable.from([10, 20, 30]) .select("x => x * x") .toArray(); // arrayは[100, 400, 900]になる
辞書の場合はkeyにキー、valueにその値が入った辞書配列として列挙されます。
Enumerable.from(%["hoge" => 100, "moge" => 200]) .forEach(function(x) { System.inform(@"キー:${x.key}, 値:${x.value}"); }); // "キー:hoge, 値:100" // "キー:moge, 値:200" // と表示される。
文字列の場合は先頭から1文字ずつ列挙されます。toString()でシーケンスを文字列に変換できます。
var str = Enumerable.from("StRinG") .where("_ == _.toUpperCase()") // 大文字のみ抽出 .toString(); // strはSRGになる。
Enumerable.extendTo
LINQを頻繁に使う場合、Enumerable.fromを毎回呼び出すのは面倒です。今のところ配列限定ですが、一度Enumerable.extendTo(Array);を呼び出せば配列からselectなどの関数を直接呼び出せるようになります。
Enumerable.extendTo(Array); var array = [1,2,3,4,5]; array = array.select("_ * 10").toArray(); // arrayは[10,20,30,40,50]になる
関数一覧
C#のLINQ to Objectsにある関数は全て移植済みです。その他にもInteractive Extensionsやlinq.jsなどから必要そうな関数を追加しています。
クエリ関数一覧
aggregate, aggregateWithSeed, all, any, average, buffer, concat, contains, count, defaultIfEmpty, distinct, do, elementAt, elementAtOrDefault, except, first, firstOrDefault, forEach, force, groupBy, groupJoin, indexOf, innerJoin, intersect, isEmpty, last, lastOrDefault, max, min, orderBy, orderByDescending, outerJoin, repeat, reverse, scan, scanWithSeed, select, selectMany, sequenceEqual, single, singleOrDefault, skip, skipWhile, startsWith, sum, take, takeWhile, thenBy, thenByDescending, toArray, toDictionary, toLookup, toString, trace, union, where, zip
生成関数一覧
Enumerable.from, Enumerable.empty, Enumerable.range, Enumerable.repeat, Enumerable.return, Enumerable.matches, Enumerable.random, Enumerable.randomInt, Enumerable.generate
Enumerable.matches
linq.tjs独自の関数としてEnumerable.matchesを追加しています。TJSではRegExpクラスで正規表現を使えますが癖があっていまいち使いづらいです。Enumerable.matchesでは正規表現のマッチ結果を扱えます。
Enumerable.matches(/([a-zA-Z]+)=([0-9]+)/g, "foo=100 bar=200") .forEach(function (matches) { System.inform(@"0:${matches[0]}, 1:${matches[1]}, 2:${matches[2]}"); }); // "0:foo=100, 1:foo, 2:100" // "0:bar=200, 1:bar, 2:200" // が表示される
第1引数は使用する正規表現です。gフラグは必須です。第2引数は正規表現の対象となる文字列です。
このようにするとRegExp.matchesの結果が列挙されます。もう正規表現を使うのにfor文は必要ありません。