JavaScript: Array.reduce()メソッドで配列要素を入れ替える

[`Array.prototype.reduce()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)は、応用範囲の広い配列操作のメソッドです。ただ、与えられるパラメータの数も多く、使い方がわかりにくいきらいは否めません。配列要素を入れ替える例がわかりやすそうかと思いついたので採り上げます。


# 数値要素の合計を求める
簡単な例からはじめます。よく使われるのが、数値の配列要素の合計です。アロー関数式`=>`で与えたコールバック関数の第1引数(`sum`)が結果の集計で、第2引数の要素(`num`)を順に取り出して集計にまとめます。合計の場合には、集計結果に要素の値を加えてゆくということです。

```js
const sum = [0, 1, 2, 3, 4].reduce((sum, num) => sum + num);
console.log(sum);  // 10
```


# 要素に処理を加えた新たな配列を返す
`Array.prototype.reduce()`の第2引数は集計の初期値です。前項の例では省きました。すると、インデックス0の値を初期値として、インデックス1から処理がはじまるのです。でも、集計値が配列などのオブジェクトになる場合には、初期値を与えなければなりません。

数値の配列要素を、それぞれ2乗するのがつぎの例です。集計の配列(`resultArray`)に要素を加えるときは、[`Array.prototype.push()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/push)を用いるのが通常でしょう。ここではあえて、コールバック関数の第3引数(`id`)を使ってみました。

```js
const array = [0, 1, 2, 3, 4].reduce((resultArray, num, id) => {
	resultArray[id] = num ** 2;
	return resultArray;
}, []);
console.log(array);  // [0, 1, 4, 9, 16]
```

もっとも、各配列要素への処理が加えられた同じ要素数の配列を返すなら、[`Array.prototype.map()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map)メソッドの方が端的です。

```js
const array = [0, 1, 2, 3, 4].map((num) => num ** 2);
```


# 条件に合った要素からなる配列を返す
`Array.reduce()`は、`Array.map()`と異なり、返す配列の要素数は問われません。たとえば、数値の配列から偶数値だけの配列をつくって返すこともできます。この場合、要素数が変わりますので、集計配列(`resultArray`)に条件に合った値を加えるのは`Array.push()`メソッドです。

```js
const array = [0, 1, 2, 3, 4].reduce((resultArray, num) => {
	if (num % 2 === 0) {
		resultArray.push(num);
	}
	return resultArray;
}, []);
console.log(array);  // [0, 2, 4]
```

ただ、条件に合った値が収められた配列をつくるには、[`Array.prototype.filter()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)メソッドが用意されています。

```js
const array = [0, 1, 2, 3, 4].filter((num) => num % 2 === 0);
```


# 配列要素を入れ替える
`Array.reduce()`の処理は一応わかっても、他の簡単なメソッドで済ませられるのならどこがいいのか、と思われるかもしれません。けれど逆に考えれば、`Array.map()`や`Array.filter()`がなかったとしても、`Array.reduce()`で同じ処理はできます。それが、応用範囲の広いメソッドだという理由です。

とはいえ、`Array.reduce()`メソッドのありがたみはわかりません。思いついた例が、配列要素を入れ替える処理です。これまでのメソッドで扱おうとすれば、[`Array.prototype.splice()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)を使うことになるでしょう。その場合、要素の削除と挿入のふたつの操作が必要です。いずれも、もとの配列の対象要素からあとのインデックスがずれてしまいます。

この要素入れ替えの処理を、つぎのような関数(`replaceArrayElements()`)として定めてみましょう。

```js
replaceArrayElements(配列, 入れ替え先インデックス, 入れ替えもとインデックス)
```

`Array.reduce()`メソッドのコールバック関数は、第4引数にもとの配列が受け取れます。そして、メソッドの戻り値は処理を終えた新たな配列です。つまり、もと配列のインデックスは崩れません。したがって、以下のように入れ替えるインデックスの要素を、差し替えさえすればよいのです。

戻り値の配列(`resultArray`)には、あえてインデックスによるブラケット`[]`アクセスも`Array.push()`メソッドも用いず、[スプレッド構文`...`](https://qiita.com/FumioNonaka/items/4ff7b392bbd8232a00ba#arrayprototypekeysメソッドとスプレッド構文を使う)で新たな配列をつくってみました。これでインデックスのずれを気にすることなく、要素の入れ替えができます。

```js
function replaceArrayElements(array, targetId, sourceId) {
	return array.reduce((resultArray, element, id, originalArray) => [
		...resultArray,
		id === targetId ? originalArray[sourceId] :
		id === sourceId ? originalArray[targetId] :
		element
	], []);
}
const array = replaceArrayElements([0, 1, 2, 3, 4], 3, 1);
console.log(array);  // [0, 3, 2, 1, 4]
```

`Array.reduce()`のパラメータすべてを用いて、このメソッドがどう役立つか示せたのではないでしょうか。

もっとも、配列要素の入れ替えというお題については、[分割代入](https://qiita.com/FumioNonaka/items/20e96ca9ede2ae157bcc#分割代入)を使った方がもう少し簡潔になり、処理負荷も下げられそうです。なお、配列は[スプレッド構文`...`](https://qiita.com/FumioNonaka/items/20e96ca9ede2ae157bcc#スプレッド構文)で複製しました。

```js
function replaceArrayElements(array, targetId, sourceId) {
	const cloneArray = [...array];
	[cloneArray[targetId], cloneArray[sourceId]] = [array[sourceId], array[targetId]];
	return cloneArray;
}
```