カメリアの記事

意味があることやないことを綴ります

JavaScript 自由研究 - 最頻値を求めるコード

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

  1. arr を参照します。
  2. 二つある this のうち前の this が arr を参照します。
  3. this は bind(new Map) によって初期化され Map オブジェクトになります。この時点で this からは重複が取り除かれます。
  4. この this を後ろの this が参照します。
 
POINT: this を「これ」だと思うと理解が阻害されます。「親を参照する」と考えると僕としては分かりやすいです。 前の this では親筋にあたる function(x, v) は関数ですので参照すべき親がありません。レキシカルスコープをたどって遡上するとメソッド reduce() があります。メソッドには親がいますので、この親である arr に行き着きます。 後ろの this も同様に関数 c のレキシカルスコープをたどってメソッド set() の親である、前の this に行き着きます。
 
  1. メソッド reduce() の引数である x と v が各所に配置されます。配置方法がこのコードの妙と言えましょう。後に関数 c の構造を見ていくと何が行われているのか分かるようになります。
  2. メソッド reduce() の引数 x の初期値が設定されます。

ステップ 2

  1. 関数 c の詳細です。関数 c は引数 x について引数 i と v を用いて処理をします。処理をしますが、戻り値はコロン演算子で区切られた最後の値 i となります。
  2. 引数 i と v が配置されます。
  3. 下のようになります。 A. x[i] が存在すれば、 B. Set オプジェクト x[i] に v を add() します。 A. x[i] が存在しなければ、 C. new Set(v) で初期化します。

ステップ 3

  1. 関数 c の戻り値として i が、メソッド Set() の第 2 引数として渡されます。
  2. 関数 function(x, v) の戻り値はコロン演算子の最後の値である x が return されます。それを受け取った reduce() は再び function(x, v) の引数 x としてリサイクルします。
  3. メソッド reduce() が arr について一通りの処理を行った結果を pop() します。 reduce() の返す最終的な配列 x ( x[i] の中身は Set )の最終項目が最頻値となります。
  4. result として結果が返されます。
  5. スプレッド演算子「 … 」によって Set である result を解体します。解体したものを [ ] を使って配列にして返します。