【TS】今さら聞けないreduce
はじめに
Javascript
もしくはTypescript
で配列を操作する際に、reduce
を使ったことがありますか?
「コアな部分で使っているのは見たことあるけど、どんな関数なのかはイマイチ分からない」という方もいるのではないでしょうか。
私自身の話になりますが、React
やReactNative
の開発プロジェクトをいくつか経験したことがあります。
たまに新入社員の方がアサインすることもあり「まずはJS
やTS
を知ろう」といったレベルから教えることもあります。
皆さん配列に関しては最初「for
やforEach
をグルグル回して処理する」というアプローチを取り、そこから段々とmap
やfilter
などを用途に合わせて覚えていきますが、大概reduce
でつまづきます。
なぜなんだろう?と考えた時「filter
やsome
に比べてreduce
という名前が直感的でない」や「reduce
は何でもできてしまうが故に取っ付きにくい」など色々と理由が浮かびます。
要するに 「何をやっているのか分かりにくい」 のだと思います。
そこで今回はTypescript
のサンプルを見ながら、配列界随一の万能関数reduce
について解説していきたいと思います。
その前に・・・
reduce
について解説する前に、その他の有名どころの関数について紹介したいと思います。
基本となるforEach
については割愛したいと思います。
また、以降のソースについては基本的には以下のようなStudent
という型と、そのインスタンス3件のデータが入った配列を用いていくこととします。
name
とscore
というプロパティを持つオブジェクト型になります。
type Student = {
name: string;
score: number;
}
const data: Student[] = [
{ name: '太郎', score: 75 },
{ name: '花子', score: 62 },
{ name: 'John', score: 59 }
]
なお、紹介する各関数について全ての引数を扱うわけではありません(例えば、コールバック関数の実行時にthis
をバインドするために引数thisArgs
等がありますが、ここでは割愛しています)。
map
新しい配列を作る
map
は元の配列から 「新しい配列」 を作ります。
map
の第一引数で渡した関数内のreturn
した値がまとまって配列になります(何もreturn
しない場合はundefiend
が入ります)。
const scoreList: number[] = data.map((d: Student): number => {
return d.score;
});
console.log(scoreList); // --> [75, 62, 59]
filter
,find
特定の要素を取得する
どちらも第一引数で渡した関数内で真を返した配列要素だけを抽出します。
filter
は配列、find
は単一要素を返します。
const under65List: Student[] = data.filter((d: Student): boolean => {
return d.score < 65;
});
console.log(under65List); // --> [{ name: '花子', score: 62 }, { name: 'John', score: 59 }];
const taro: Student = data.find((d: Student): boolean => {
return d.name === '太郎';
});
console.log(taro); // --> { name: '太郎', score: 75 }
some
,every
配列そのものが目的条件に合致しているかを判定する
どちらも真偽値を返却します。
配列中の値に対して第一引数の関数を実行していき、some
は「1件でも条件を満たしていたら」、every
は「全件条件を満たしていたら」true
を返します。
const someOver70: boolean = data.some((d: Student): boolean => {
return d.score > 70;
});
console.log(someOver70); // --> true
const everyOver70: boolean = data.every((d: Student): boolean => {
return d.score > 70;
});
console.log(everyOver70); // --> false
reduce
新しい単一要素を返す
いよいよreduce
についての説明です。
reduce
は使い勝手としてはmap
に近いかなと思います。
第一引数には最低2つの引数を取る関数を渡します。
この2つの引数はそれぞれ 「アキュムレーター」 と 「現在の値」 と呼ばれています。
アキュムレータには、「初期値」もしくは「直前の処理で返された値」が入り、現在の値にはその名のとおり「今現在処理している配列要素」が入ります。
また、第二引数には「アキュムレータ」の初期値を指定します。
// 全員のscoreの合計を取得する
const totalScore: number = data.reduce((acc: number, val: Student): number => {
// accは「初期値 or 前回のreturn値」でvalは「配列要素」
return acc + val.score;
}, 0);
console.log(totalScore); // --> 196
reduce
がなぜ万能と言われるかというと、今まで紹介した関数をreduce
で置き換えることができるからだと思います。
例えばmap
の例のようにscore
の値の配列を取得したい場合は下記のようにすることで実現できます。
const scoreList: number[] = data.reduce((acc: number[], val: Student): number[] => {
return [...acc, val.score];
}, []);
console.log(scoreList); // --> [ 75, 62, 59 ]
同様にしてfilter
やfind
,some
,every
も下記のように置き換えることができます。
const under65List: Student[] = data.reduce((acc: Student[], val: Student): Student[] => {
if(val.score < 65) return [...acc, val];
return acc;
}, []);
console.log(under65List); // --> [{ name: '花子', score: 62 }, { name: 'John', score: 59 }];
const taro: Student | undefined = data.reduce((acc: Student | undefined, val: Student): Student | undefined => {
if(acc) return acc;
return val.name === '太郎'? val : undefined;
}, undefined);
console.log(taro); // --> { name: '太郎', score: 75 }
const someOver70: boolean = data.reduce((acc: boolean, val: Student): boolean => {
if(acc) return acc;
return val.score > 70;
}, false);
console.log(someOver70); // --> true
const everyOver70: boolean = data.reduce((acc: boolean, val: Student): boolean => {
if(!acc) return acc;
return val.score > 70;
}, true);
console.log(everyOver70); // --> false
初期値
第二引数の初期値は省略可能です。
その場合、アキュムレータの初期値には「配列の0番目の要素」が入り、現在の値は「配列の1番目の要素」から始まります。
ループの開始位置が変わるため、初期値を設定しない方が設定した場合に比べてループ回数が1減ります。
- 初期値なし
ループ回数 | acc | val |
---|---|---|
1 | 0番目の要素 | 1番目の要素 |
2 | 1回目のループのreturn値 | 2番目の要素 |
- 初期値あり
ループ回数 | acc | val |
---|---|---|
1 | 初期値 | 0番目の要素 |
2 | 1回目のループのreturn値 | 1番目の要素 |
まとめ
今回はreduce
について、Typescript
のコードベースで解説を行いました。
何でもできてしまうが故によく分からない、となりがちな関数ですが一度覚えて使いこなせば非常に役立つと思います。
※なお、今回はパフォーマンスについては言及せず、挙動についての解説に留めています。
この記事がreduce
やその他関数の学習に役立てば幸いです。
Discussion