JavaScript には filter, map, reduce などの配列を扱う便利な関数がいくつかありますが,他のモダンな言語にはあるけど JavaScript にはない関数というのもあります。今回はその中で collect
と scan
という関数を自作してみます。
collect
map と flat を合わせたようなものです。
各要素に対して指定された関数を適用し,結果を連結したリストを返します。
参考:https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-listmodule.html#collect
(2020/10/26 追記)
コメントいただきました。普通に同じ意味の flatMap
というのがありますね。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
ランタイムが古くて使えないなどの理由がない限りは必要ないですね。
※ また,その場合でも「大きな配列の場合は避けるべきである」と上記リンクで警告されてます。
ソースコード
const collect = (list, fn) => (
list && list.map(fn).reduce((acc, x) => acc.concat(x), [])
);
Array.prototype.collect = function(fn) {
return collect(this, fn);
}
実行例
[3, 4, 5].collect(x => [...new Array(x).keys()].map(x => x + 1))
// > [1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5]
任意の数を受け取ると1からその数までの配列を生成する関数を適用しています。
適用する関数では 3 なら [1, 2, 3] が,4 なら [1, 2, 3, 4] が返りますが,collect はそれらを展開した結果を返します。
scan
fold (reduce) に似ていますが,最終的な結果だけでなく,途中経過の結果の配列を返します。
参考:https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-listmodule.html#scan
ソースコード
const scan = (list, fn, initialValue) => (
list && list.reduce(
([acc, scanAcc], cur, idx, src) => {
const ret = fn(acc, cur, idx, src);
scanAcc.push(ret);
return [ret, scanAcc];
},
[initialValue, []]
)[1]
);
Array.prototype.scan = function(fn, initialValue) {
return scan(this, fn, initialValue);
}
実行例
[1, 2, 3, 4, 5].scan((a, x) => a + x, 0)
// > [1, 3, 6, 10, 15]
アキュムレーター a
に各要素 x
を加算する関数と初期値 0
を指定します。
0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10 + 5 = 15
このような順に計算されていきます。fold (reduce) では,最終結果 15 が返却されますが,scan の場合は途中結果も含めて各要素に適用した結果が配列として返却されます。