JavaScriptで２つの配列からそれぞれをキーと値としてオブジェクトを作った話【初心者向け/reduce()入門】

#はじめに

JavaScriptでデータを取得して整形することはよくあると思います。

扱いやす形のものばかりなら良いですが、
そうではないのものも多くあるのがこの世の常です。

なので、こちらで扱いやすい形にいじってあげる。
ということも当たり前のように発生します。

あるオブジェクトのキーを取得して配列化したり、
あるオブジェクトの値を取得して配列化したり、
配列から特定の要素に絞り込んで再び配列化したり。。。

そんな中、とあるデータ操作に躓いたので、
解決策を共有します。

当記事を読んでいて配列とかオブジェクトそのものの理解が怪しいよという方は、
拙著の過去記事
https://qiita.com/shigematsu_k/items/19a47ff2a8fa66e23a3a
も読んでみてください。

#本題

ここからはタイトル通りです。
2つの配列があり、
うち１つの配列をキー、
もう１つを値として新たなオブジェクトを作りたい時にハマりました。

下のイメージです。


```javascript

//オブジェクトのキーにしたい配列
const array1 = ['弾道', 'ミート', 'パワー', '走力', '肩力', '守備力', '捕球'];
//オブジェクトの値にしたい配列
const array2 = [4, 'C', 'A', 'B', 'A', 'D', 'E'];

//２つの配列を使ってこんなオブジェクトを作りたい。
const giita = {
  弾道: 4,
  ミート: C,
  パワー: A,
  走力: B,
  肩力: A,
  守備力: D,
  捕球: E
}
```

ゴールはQiitaではなくギータです。
（ホークス優勝おめでとう。）


#解決策

いきなり結論です。
今回は`reduce()`メソッドを使って解決しました。

以下が解決結果となります。

```javascript
const array1 = ['弾道', 'ミート', 'パワー', '走力', '肩力', '守備力', '捕球'];
const array2 = [4, 'C', 'A', 'B', 'A', 'D', 'E'];

const giita = array2.reduce((accumulator, currentValue, index) => {
  accumulator[array1[index]] = currentValue;
  return accumulator;
}, {});

console.log(giita);
```

**`console.log(giita);`の出力結果**

```text
Object {
  パワー: "A",
  ミート: "C",
  守備力: "D",
  弾道: 4,
  捕球: "E",
  肩力: "A",
  走力: "B"
}
```

はい。
これでオブジェクトにできました。（たった４行！）
オブジェクトなので、並び順は良いとして
ただ、どういう理屈でこうなったのか。
全くわかってません。

全くわかってないまま実装するのはヒエーッですよね。(ヒエーッ)
何がどうなってこのような結果が得られているのかは理解したいです。
なので少し調べます。

#正体不明の`reduce()`メソッド

>reduce() メソッドは、配列の各要素に対して (引数で与えられた) reducer 関数を実行して、単一の出力値を生成します。

>reducer 関数は 4 つの引数を取ります。
アキュムレーター (acc)
現在値 (cur)
現在の添字 (idx)
元の配列 (src)
reducer 関数の返値はアキュムレーターに代入され、配列内の各反復に対してこの値を記憶します。最終的に単一の結果値になります。

>**引数**
**callback**
配列のすべての要素 (initialValue が提供されなかった場合は、最初を除く) に対して実行される関数です。
これは 4 つの引数を取ります。

>　**accumulator**
callback の返値を蓄積するアキュームレーターです。これは、コールバックの前回の呼び出しで返された値、あるいは initialValue が指定されている場合はその値となります。
　**currentValue**
現在処理されている配列の要素です。
　**index** （Optional）
現在処理されている配列要素のインデックスです。initialValue が指定された場合はインデックス 0 から、そうでない場合はインデックス 1 から開始します。
　**array** （Optional）
reduce() が呼び出された配列です。

>**initialValue** （Optional）
callback の最初の呼び出しの最初の引数として使用する値。initialValue が与えられなかった場合、配列の最初の要素がアキュムレーターの初期値として使用され、currentValue としてスキップされます。空の配列に対して reduce() を呼び出した際、initialValue が指定されていないと TypeError が発生します。

引用元:MDN web docs Array.prototype.reduce()[^1]

:thinking_face::thinking_face::thinking_face::thinking_face::thinking_face:

ナンジャコレ状態です。はい。

ある程度読み進めると、`reduce()`は本当になんでもできそうなヤバいやつということがわかります。
それと同時に、凡人には一度に全てを理解するのは不可能ということもわかります。

なので、とりあえず今回の処理だけ追っかけましょう。

#今回のケースの`reduce()`メソッド

上記の引用文を分解して追っかけることにします。
ポイントは2つです。

##ポイントその１　**第一引数`accumulator`**

ポイントその１です、
引用文を読んでみると

>配列内の各反復に対してこの値を記憶します。


とあります。

また、`reduce()`メソッドの第１引数である`accumulator`
についての記述箇所を読むと、

>**accumulator**
callback の返値を蓄積するアキュームレーターです。これは、コールバックの前回の呼び出しで返された値、あるいは initialValue が指定されている場合はその値となります

ということなので、
どうやらコールバックの第１引数であるアキュームレーターに返値が蓄積（記憶）され、

>最終的に単一の結果値になります

最終的に１つの値となっているようだとわかります。

実際どう蓄積（記憶）しているのか
`reduce()`に渡した各引数の状態を`console.log`で覗きます。
（今回の場合は第４引数であるarrayは渡しておりません。）

```javascript
const array1 = ['弾道', 'ミート', 'パワー', '走力', '肩力', '守備力', '捕球'];
const array2 = [4, 'C', 'A', 'B', 'A', 'D', 'E'];

const giita = array2.reduce((accumulator, currentValue, index) => {
  accumulator[array1[index]] = currentValue;

  //覗いてみよう！
  console.log(accumulator,currentValue,index);
  
  return accumulator;
}, {});

```

**`console.log(accumulator,currentValue,index);`の出力結果**

```text
Object {
  弾道: 4
} 4 0

Object {
  ミート: "C",
  弾道: 4
} "C" 1

Object {
  パワー: "A",
  ミート: "C",
  弾道: 4
} "A" 2

Object {
  パワー: "A",
  ミート: "C",
  弾道: 4,
  走力: "B"
} "B" 3

Object {
  パワー: "A",
  ミート: "C",
  弾道: 4,
  肩力: "A",
  走力: "B"
} "A" 4

Object {
  パワー: "A",
  ミート: "C",
  守備力: "D",
  弾道: 4,
  肩力: "A",
  走力: "B"
} "D" 5

Object {
  パワー: "A",
  ミート: "C",
  守備力: "D",
  弾道: 4,
  捕球: "E",
  肩力: "A",
  走力: "B"
} "E" 6
```

はい、見えてきました。

array2をindex順に回しています。
そして`accumulator`のキーとして、`array1[index]`を設定し、そこに現在値を値として格納しているわけです。

もっと具体的にいうと、
初回処理の引数indexは0で、現在値（currentValue）は4になっています。
そして
`accumulator[array1[index]] = currentValue;`
で`accumulator`のキーとして、`array1[0]`つまり`'弾道'`に現在値である`4`を格納して初回の処理を終えます。

このように処理を`array2.length`の数だけ記憶しながら続け、最終的な結果を返す。ということですね。


##ポイントその２　**`initialValue`**

ポイントその１でどういう処理が行われているかはつかめました。

しかしながら、
そもそもなぜオブジェクト型で蓄積され、
最終的に欲しい形を得られているのでしょうか。

そこで、
もう１つ重要なポイントが**`initialValue`**です。
引用文該当箇所は下記の通り。

>**initialValue** （Optional）
callback の最初の呼び出しの最初の引数として使用する値。initialValue が与えられなかった場合、配列の最初の要素がアキュムレーターの初期値として使用され、currentValue としてスキップされます。空の配列に対して reduce() を呼び出した際、initialValue が指定されていないと TypeError が発生します。

この場合の`callback の最初の呼び出しの最初の引数として使用する値`として設定されているものは、
一体なんでしょうか。

```javascript
const giita = array2.reduce((accumulator, currentValue, index) => {
  accumulator[array1[index]] = currentValue;
  return accumulator;
//↓↓initialValueに注目↓↓
}, {});
```

そうです。空のオブジェクトです。
初回処理の引数として空のオブジェクトを渡すことで、
その中で、キーを設定・値を格納を蓄積し1つの値として返す、
ということになり求めているオブジェクトが得られているわけですね。

実際のコードは割愛しますが、initialValueを設定していない場合の
`giita`はただ単に配列の最初の値（`array2[0]`）を返すだけとなり出力結果は`4`となります。

ここまで確認できたら今回の場合は大丈夫。
なんとか理解が実装に耐えられそうです。

##おまけ

もし、array1とarray2でlengthが異なる場合はどうでしょうか。
一応確認しましょう。

###array2に値が１つ多い場合

```javascript
const array1 = ['弾道', 'ミート', 'パワー', '走力', '肩力', '守備力', '捕球'];
//array2に１つ追加
const array2 = [4, 'C', 'A', 'B', 'A', 'D', 'E','F'];

const giita = array2.reduce((accumulator, currentValue, index) => {
  accumulator[array1[index]] = currentValue;
  
  return accumulator;
}, {});

  console.log(giita);
```
**`console.log(giita);`の出力結果**

```text
Object {
  undefined: "F",
  パワー: "A",
  ミート: "C",
  守備力: "D",
  弾道: 4,
  捕球: "E",
  肩力: "A",
  走力: "B"
}
```

この場合array2に対して`reduce()`処理を加えているので、
当然１つ追加されたもの（この場合`'F'`）も値としてキーに格納しようとしますが、
キーが定義されていない状態なのでこのような出力結果になります。


###array1に値が１つ多い場合
逆の場合はこのようになります。

```javascript
//array1に１つ追加
const array1 = ['弾道', 'ミート', 'パワー', '走力', '肩力', '守備力', '捕球','ファンサービス'];
const array2 = [4, 'C', 'A', 'B', 'A', 'D', 'E'];

const giita = array2.reduce((accumulator, currentValue, index) => {
  accumulator[array1[index]] = currentValue;
  
  return accumulator;
}, {});

  console.log(giita);
```

**`console.log(giita);`の出力結果**

```text
Object {
  パワー: "A",
  ミート: "C",
  守備力: "D",
  弾道: 4,
  捕球: "E",
  肩力: "A",
  走力: "B"
}
```

はい。
`array2.length`分しか処理は走らないのでキーとして使いたい配列array1の最後の値（この場合`'ファンサービス'`）は当然無視されます。

#最後に

`reduce()`メソッドは調べていると、本当になんでもできそうです。
配列オブジェクトの持つメソッドの中でも最強と言えるかもしれません。。

配列を扱うとき、他のメソッドに頼らず`reduce()`で全てに対応できれば・・
なんて夢想しつつ今後も勉強していきたいです。

最初はなんだか良くわからなかったアキュームレーターについて調べると、
どうやら蓄圧器のことのようです。
もっとよくわからないですね！(ヒエーッ)
蓄積して圧縮する。くらいのノリでとらえておきます。
（MDNの記事もアキュムレーターとアキュームレーターで表記揺れしてるし..ブツブツ...）
参考リンクに貼っておきます。


##参考リンク

https://kde.hateblo.jp/entry/2018/10/13/065738#Arrayprototypereduce-%E3%81%AE%E6%A6%82%E8%A6%81
https://jsprimer.net/basic/loop/#const-iteration
https://ja.wikipedia.org/wiki/アキュムレータ_(機械)



[^1]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce