reduce 関数って使ってますか?
JavaScript の数ある配列操作の中で最もよくわからん奴・・・じゃないですか?
私はなんとなく、単に合計を求めたりする為のものだと思ってました
でも実際使ってみるとかなり便利だったので、皆さんにも完全に理解してもらうべく立ち上がりました
気分はスーパーヒーローです
※ この記事はレジェンドオブトゥモローシーズン 2 を見ながら書いたので少しだけヒーロー成分が入っています
reduce 関数って?
MDN の例文を少しわかりやすく書いています
array.reduce( ( previousValue, currentValue[, currentIndex[, originalArray]] ) { //callbackFunction
// return result from executing something for accumulator or currentValue
}
[, initialValue]);
いやわかりにくい!
1 つ 1 つ説明します
第一引数 : callbackfunction
他の配列関数(forEach とか map とか)と同じで、配列の 1 レコードずつに対して行う処理を記述します
最後のループの時に return する値が reduce メソッドの戻り値になります
1 つ 1 つ引数を見ていきましょう
引数
previousValue
前回のループの return 値です
currentValue
こちらは任意
元の配列の、現在のループの要素です
currentIndex
元の配列の、現在のループのインデックスです
originalArray
元の配列が渡されます
第二引数 : initialValue
reduce 関数の第二引数である initialValue は
後述の callbackfunction の引数に影響します
initialValue を渡すかは任意です
initialValue を渡した場合、渡さなかった場合の previousValue と currentValue の1ループ目の値の比較
引数 | 渡した場合 | 渡さなかった場合 |
---|---|---|
previousValue | initialValue | 元の配列の最初の要素 |
currentValue | 元の配列の最初の要素 | 元の配列の2番目の要素 |
実際どう使うの?
結局 reduce 関数、どう使えばいいの?
と思ったそこのあなた
安心してください
用意してますよ(実際のソースを)
超定番 値の合計を求める
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const sum = array.reduce((previousValue, currentValue) => previousValue + currentValue)
console.log(sum) // 55
どうですか?素晴らしいでしょう
何が素晴らしいか、reduce を使わない
よくあるやり方を見てみましょう
let foolishSum = 0
for (const i = 0; i < array.length; i++) {
foolishSum += array[i]
}
何がダメかは一目瞭然ですよね
let
こいつです
再代入可能なせいで可読性を悪くする悪の根源ですね(言い過ぎ)
reduce は ループした末の結果が戻り値になるので再代入をする必要がありません
でもこの程度の計算しか出来ないなら別に for で良くない?
と思うかもしれません
確かに大体の記事の reduce の説明は(他の配列操作関数も) このような計算が多いです
でもこの関数は実はなんでもできるんです
次は実務で良く使えそうなやり方をお見せします
オブジェクト配列を操作する
const list = [
{
name: "Flash",
power: "The fastest man in the world",
},
{
name: "Arrow",
power: "Master of sniper",
},
{
name: "Super Girl",
power: "Alien of Crypton",
},
{
name: "Legends of Tommorow",
power: "Overhanging heroes",
}
];
const object = list.reduce((previousValue, currentValue) => {
const name = currentValue.name;
const power = currentValue.power;
previousValue[name] = power;
return previousValue;
}, {});
console.log(object);
/*
{
Arrow: "Master of sniper",
Flash: "The fastest man in the world",
Legends of Tommorow: "Overhanging heroes",
Super Girl: "Alien of Crypton"
}
*/
アローバースのヒーローの情報を取得する API のレスポンスを操作する
と言うイメージです
アローバース最高!!
initialValue に空のオブジェクト渡しています
空のオブジェクトに、各ループで
key=name,value=power のプロパティを push しています
最後に戻り値を const で宣言した変数に入れている為、それ以降操作する必要がなくなります
これを普通にやろうとするとこうなります
const uglyObject = {}
for (let i = 0; i < list.length; i++) {
const object = list[i]
const name = object.name
const power = object.power
uglyObject[name] = power
}
いやまあ確かに const になっていますが
JavaScript のオブジェクトや配列が const なのに後から追加できちゃうのはどうなんだ・・・
できるからと言って const で宣言しているオブジェクトに追加するのは良くないですね
このように reduce 関数であれば可読性を失わずにオブジェクトを操作したりできます
reduce 関数さえあれば、他の配列操作関数で出来る大概のことはできます
他の関数の模倣
reduce 関数でいろんな配列操作関数の模倣をしてみます
その前に・・・
配列関数にはそれぞれの意図があります
reduce 関数や forEach 関数は割となんでもできます
でもなるべくそれぞれの処理にあった配列を関数を使った方が、何をしているかが一目瞭然です
それに、実際のソースをみてもらえばわかりますが各関数を使うよりステップ数が多くなります
可読性が悪いですね・・・
じゃあなんで模倣のやり方を書くのかって?
実際に何かを作る時、いつかはゴリ押ししないといけない時が必ずあります・・・
そんなどうしようもない時の技の一つとして
必要な時にあんなやり方書いてあったな・・・
と思い出してもらえれば良いなと思います
以上を理解した上でこの先を読んでください
find
対象の配列から条件に合う要素を抽出する奴です
const findResult = list.find((object) => object.name === "Flash");
const findAlternative = list.reduce((previousValue, currentValue) => {
const isFlash = currentValue.name === "Flash"
const alreadyFound = previousValue.name !== "Flash";
return isFlash && alreadyFound ? currentValue : previousValue;
});
find 関数は一つ該当する要素を見つけたらその時点でループが終了します
配列関数は break 等ができず、reduce 関数は最後までループが続いてしまいます
そのため上の例では既に該当する要素を見つけている場合
以降は結果を取得しないようにしています
かなりゴリ押しですが、find 関数と同じ事ができました
filter
対象の配列から条件に合う要素全てを抽出して、新しい配列を生成する奴です
const list = [
{
name: "Barry Allen",
team: "Team Flash",
},
{
name: "Oliver Queen",
team: "Team Arrow",
},
{
name: "John Diggle",
team: "Team Arrow",
},
{
name: "Caitlin Snow",
team: "Team Flash",
},
{
name: "Cisco Ramon",
team: "Team Flash",
},
{
name: "Felicity Smoak",
team: "Team Arrow",
},
];
const filterResult = list.filter((object) => object.team === "Team Flash");
const filterAlternative = list.reduce((previousValue, currentValue) => {
if (currentValue.team === "Team Flash") {
previousValue.push(currentValue);
}
return previousValue;
}, []);
map
対象の配列から新しい配列を生成できるやつです
うまい例が思いつきませんでした・・・悔しい
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const mapResult = array.map((value) => value * 2);
const mapAlternative = array.reduce((previousValue, currentValue) => {
previousValue.push(currentValue * 2);
return previousValue;
}, []);
第三、第四引数の使い道
お気付きかと思います
これまでの例は全て第一、第二引数しか使ってませんね
じゃあ第三、第四引数はどう使うの?と思うかもしれません
正直いまいち使い道を見つけたことがあまりありません・・・
ただ、各ループでの previousValue と currentValue 以外の要素を使う事が出来るんじゃないかと思います
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = array.reduce((previousValue, currentValue, currentIndex, originalArray) => {
const nextIndex = currentIndex + 1;
if (originalArray[nextIndex]) {
previousValue.push(String(currentValue) + String(originalArray[nextIndex]));
}
return previousValue;
}, []);
// ["12", "23", "34", "45", "56", "67", "78", "89", "910"]
これまたうまい例が思いつかなかったので普通に数字だけの配列です
originalArray と currentIndex を利用して現在のループの次の要素を取得しています
何かうまい使い道があれば是非教えて欲しいです
ちなみに originalArray は元の array と同一の参照になるので、下手に操作するとぶっ壊れます(下記参照)
基本的に originalArray に対して手を加えることはしない方が良さそうです
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = array.reduce((previousValue, currentValue, currentIndex, originalArray) => {
originalArray[currentIndex] = 100
previousValue.push(currentValue)
return previousValue
}, []);
console.log(array) // [100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
console.log(result) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
後書き
reduce 関数はただ計算結果を出力するだけでなく、ループ間で操作した配列やオブジェクトを返却する使い勝手の良い関数です
ちゃんと理解して使えばかなり便利(ゴリ押しも効く)なので
要法要領を守って正しく使用し、より良い配列操作ライフを送ってください
また、DC ドラマシリーズはネトフリやアマプラで見れるので是非みてください