情報システム部の高野です。
今日は、JavaScriptのライブラリ「Lodash」について書きます。
Lodashは、色々なことができるライブラリなのですが
私は、集合を扱う関数(C#でいうLINQ to Object)しかほぼ使っていません。
この集合を扱う関数で2種類の書き方があります。
※以降で扱うソースコードは、TypeScriptで記述します。
1つ目の書き方は、_メソッドの引数に配列を書く方法です。
_([1,2,3,4]).filter(x => x % 2 == 0).map(x => x * x).forEach(x => console.log(x)); // 4 // 16
2つ目の書き方は、filterなどのメソッドの引数に配列を書く方法です。
_.filter([1,2,3,4], x => x % 2 == 0).map(x => x * x).forEach(x => console.log(x)); // 4 // 16
この2つは、どちらも結果は同じになります。(違ったら困りますね)
では、なぜ2種類の書き方があるのかと言うと
1つ目は、遅延実行されるのです。
どういうことかを確認するため下記のように書き換えます。
_([1,2,3,4]).filter(x => { console.log("filter:" + x); return x % 2 == 0; }).map(x => { console.log("map:" + x * x); return x; });
これは、なにも出力されません。
_.filter([1,2,3,4], x => { console.log("filter:" + x); return x % 2 == 0; }).map(x => { console.log("map:" + x * x); return x; }); // filter:1 // filter:2 // filter:3 // filter:4 // map:2 // map:4
こちらは、普通に出力されます。
遅延実行は、必要な時がくるまで実行されないのです。
ではどうやって実行するかですが、下記のように書きます。
var a = _([1,2,3,4]).filter(x => { console.log("filter:" + x); return x % 2 == 0; }).map(x => { console.log("map:" + x * x); return x; }); a.value(); // LINQのToArray()と同じ // filter:1 // filter:2 // filter:3 // filter:4 // map:2 // map:4
valueメソッドやforEachメソッド、sizeメソッドなどで処理が実行されます。
ちなみにmapメソッドの後にvalueメソッドを続けて書くことも、もちろんできます。
出力結果を見てC#を知っている方は違和感を持ったのではないでしょうか?
ここでC#で同じことをやってみます。
var sample = new []{1, 2, 3, 4}; sample.Where(x => { // filterは、linqではWhere WriteLine($"filter:{x}"); return x % 2 == 0; }).Select(x => { // mapは、linqではSelect WriteLine($"map:{x * x}"); return x; }).ToArray(); // filter:1 // filter:2 // map:4 // filter:3 // filter:4 // map:16
mapが出てくるタイミングが違いますね。
C#のLINQでは、ストリーム的に1つずつ次の処理に投げているのに対して
Lodashの場合は、1つのメソッドの処理を終えてから次のメソッドに投げています。
では、JavaScriptでは、ストリーム的に処理を流すことはできないのか?
linq.jsというライブラリで試してみましょう。
linq.from([1,2,3,4]).where(x => { console.log("filter:" + x); return x % 2 == 0; }).select(x => { console.log("map:" + x * x); return x; }).toArray(); // filter:1 // filter:2 // map:4 // filter:3 // filter:4 // map:16
C#とまったく同じになります。
それじゃlinq.jsの方が効率も良いし高速なのでは?
計測してみましょう。
このようなプログラムを作成し
Lodashの遅延実行・即時実行、linqjsで比較してみます。
let sample = linq.range(1, 100000).toArray(); for(let i = 0; i < 10; i++) { console.time("timer"); _(sample).filter(x => x % 2 == 0).map(x => x * x).value(); console.timeEnd("timer"); }
10回の内の最大値と最小値を抜いた8回の平均です。
- Lodash遅延実行・・・4.97ms
- Lodash即時実行・・・26.6ms
- linqjs・・・44.5ms
予想に反してLodashの遅延実行が一番速かったです。
ということでLodash使うなら基本的には遅延実行の方を使った方が良いでしょう。
でもRemoveメソッドとかは、即時実行の方が良いケースが多いと思うのでちゃんと使い分けるってことですかね。
※10万回ループしての差なので業務システムなどの場合は、どちらを選んでも大差無いと思われます。