Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

reduce関数完全に理解した

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 ドラマシリーズはネトフリやアマプラで見れるので是非みてください

azuharu07
ちょっと特殊なポジションで働いていて気づいたこととか学んだことを色々まとめていきます Laravel勉強中
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away