「javascript 配列の最頻値を求める | mebee」にあった以下のコードについて研究しました。
'use strict'
const arr = ["a", "a", "a", "b", "c", "d", "d", "e", "e", "e", "e"];
const c = (x, i, v) => (x[i] ? x[i].add(v) : x[i] = new Set(v), i);
const result = arr.reduce(function (x, v) { return (this.set(v, c(x, (this.get(v) + 1 || 1), v)), x); }.bind(new Map), []).pop();
console.log([...result]); // ['e']
概要
概要は下のとおりです。なかなかややこしいので 3 ステップに分けて解説します。 ‘use strict’ はなくても動くようなので省略しています。画像は拡大して表示することができます。
ステップ 1
- arr を参照します。
- 二つある this のうち前の this が arr を参照します。
- this は bind(new Map) によって初期化され Map オブジェクトになります。この時点で this からは重複が取り除かれます。
- この this を後ろの this が参照します。
POINT: this を「これ」だと思うと理解が阻害されます。「親を参照する」と考えると僕としては分かりやすいです。 前の this では親筋にあたる function(x, v) は関数ですので参照すべき親がありません。レキシカルスコープをたどって遡上するとメソッド reduce() があります。メソッドには親がいますので、この親である arr に行き着きます。 後ろの this も同様に関数 c のレキシカルスコープをたどってメソッド set() の親である、前の this に行き着きます。
- メソッド reduce() の引数である x と v が各所に配置されます。配置方法がこのコードの妙と言えましょう。後に関数 c の構造を見ていくと何が行われているのか分かるようになります。
- メソッド reduce() の引数 x の初期値が設定されます。
ステップ 2
- 関数 c の詳細です。関数 c は引数 x について引数 i と v を用いて処理をします。処理をしますが、戻り値はコロン演算子で区切られた最後の値 i となります。
- 引数 i と v が配置されます。
- 下のようになります。 A. x[i] が存在すれば、 B. Set オプジェクト x[i] に v を add() します。 A. x[i] が存在しなければ、 C. new Set(v) で初期化します。
ステップ 3
- 関数 c の戻り値として i が、メソッド Set() の第 2 引数として渡されます。
- 関数 function(x, v) の戻り値はコロン演算子の最後の値である x が return されます。それを受け取った reduce() は再び function(x, v) の引数 x としてリサイクルします。
- メソッド reduce() が arr について一通りの処理を行った結果を pop() します。 reduce() の返す最終的な配列 x ( x[i] の中身は Set )の最終項目が最頻値となります。
- result として結果が返されます。
- スプレッド演算子「 … 」によって Set である result を解体します。解体したものを [ ] を使って配列にして返します。