reduce の使用例を振り返る【JavaScript】
明けましておめでとうございます!
JavaScriptの基礎力を上げようと、年末年始休暇を利用してプログラミングの問題を解いていました。
その中で、reduceメソッドの使い方を覚えたのですが、それ以降の問題はreduceが使いたくなってしまい、できるだけreduceを使って問題を解いていました。新しい技を覚えたら使いたくなりますよね笑
しかし落ち着いて考えてみると、そんなにあちこちでreduceを使うのが本当により良い選択だったのかは定かではありません。
そこで、今回は実際に書いたコードを3つ振り返り、本当にreduceを使うべきだったのか、他の選択肢はどんなものだったのかといったことを考えてみたいと思います。
reduce について
reduceの詳細はこちらでの確認をお願いします
自分なりに簡単にまとめると
- reduceは配列に対するメソッド
- 引数としてコールバック関数が必須、またオプションで初期値が指定できる
- 返り値は単一の値になる
構文は次のようなパターンがあります
reduce(callbackFn)
// もしくは
reduce(callbackFn, initialValue)
例1. 合計ポイント数を求める
では実際のコードを見ていきます。
コードの背景:
与えられる注文リストから正しい還元ポイント数を求めたい。注文は商品ジャンルと商品の金額のデータからなっており、ジャンルごとに還元率が異なる
// 注文リスト orders の形式: [[0, 150], [2, 2200], [3, 1800], ...]
const rateTable = { 0: 5, 1: 3, 2: 2, 3: 1 }; // ジャンルを表す数字とその還元率の表
const points = orders.reduce((acc, [genre, total]) => {
const point = Math.floor(total / 100) * rateTable[genre];
return acc + point;
}, 0); // 初期値 0 を設定
やっていること
- 初期値を0に設定(ここにポイントを足していく)
- 注文リスト orders の各要素に対して以下の処理を行う
- 要素に対してポイントを計算
- そのポイントを合計値に加算
reduce を使うことは適切か?
問題ないと思います。
上であげたmdnのページでは、reduce の使用例として配列のすべての要素の和を求めるコードが載っています。それと比べると、今回のコードはいくつか条件が違ってはいますが、根本的な部分は同じなのではないかと思います。
- 違う部分
- 配列の要素が数字ではなく数字を含む配列であること
- ループの中で還元率を求める計算を行っていること
- 同じ部分
- 配列の各要素をもとにした数字の和を求めていること
mdnのページの例を転載します。
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue,
);
console.log(sumWithInitial);
// Expected output: 10
例2. 最高と最低を求める
コードの背景: 数日間の気温データの配列からその期間の最高気温と最低気温を取得したい
// dataの例: [[22, 15], [26, 21], [25, 18]]
const highAndLow = data.reduce(
(acc, daydata) => ({
high: Math.max(acc.high, daydata[0]),
low: Math.min(acc.low, daydata[1]),
}),
{ high: 0, low: Infinity } // 初期値
);
console.log(highAndLow); // { high: 26, low: 15 }
やっていること
- 初期値に最高と最低を記録していくためのオブジェクトを設定
- 気温データの配列 data の各要素に対して以下の処理を行う
- 今回の要素 daydata の気温と記録している気温を比較して新たなデータを記録
reduce を使うことは適切か?
悪くはなさそうだけど、for...of文を使ってもよかったかもしれない
for...of文で書くとこうなります
let highAndLow = { high: 0, low: Infinity }; // 初期値
// dataの例: [[22, 15], [26, 21], [25, 18]]
for (const daydata of data) {
const daydata = data[i];
highAndLow.high = Math.max(highAndLow.high, daydata[0]);
highAndLow.low = Math.min(highAndLow.low, daydata[1]);
}
console.log(highAndLow); // { high: 26, low: 15 }
- reduce
- メリット: 初期値を関数内で設定できるのでコードが簡潔になる
- デメリット: 可読性がやや下がるかも
- for...of
- メリット: 処理が複雑でない分、可読性が高い
- デメリット: ない(初期値の設定を関数外で行っているがそれほど問題ではない気がする)
改めて考えると、reduceを使う必要性はなかったようです
例3. 2次元配列を1次元配列にする
コードの背景: 2次元配列を1次元配列にしたい
// arrInChangeの例: [[1, 2, 3], [4, 5, 6], [7, 8]]
targetArr = arrInChange.reverse().reduce((acc, ele) => {
return acc.concat(ele);
}, []); // 初期値は空配列
やっていること
- 2次元配列の各要素を空配列に順番に追加している
reduce を使うことは適切か?
適切ではない
flat
を使う方が良いため
targetArr = arrInChange.reverse().flat();
(flatメソッド、知りませんでした…)
上の方で上げたサイトに、reduceを使うべきでない例としてこの処理がまるまる掲載されていました。何でもかんでもreduceを使っちゃダメですね😅
reduce() よりも優れた代替手段が存在する場合があります。
配列の平坦化。代わりに flat() を使用してください。
const flattened = array.reduce((acc, cur) => acc.concat(cur), []);
おわりに
見直してみると、やはりわざわざ使う必要のないところでreduceを使っていたことがわかりました。
reduceを使う際は、便利さと可読性のバランスを考えることが大事なのだと思います。
Discussion