reduce で積を計算
集計のたびに、入れ物の変数を用意している
一番長い単語を選びたい、合計を出したい、文字列をつなげたい。書いているコードは毎回よく似ています。
JavaScript
let longest = "";
for (const word of words) {
if (word.length > longest.length) longest = word;
}答えを入れる変数を作り、初期値を決め、1 周まわして、最後に返す。この骨組みが集計のたびに現れます。変わっているのは真ん中の 1 行だけなのに、周りの 3 行を毎回書き直しています。
1 つの値に畳む
reduce は、この骨組みごと引き受けてくれます。渡すのは 2 つだけです。1 つは「ここまでの答えと次の要素から、新しい答えを作る関数」。もう 1 つは「最初の答え」です。
JavaScript
const longest = words.reduce(
(best, word) => (word.length > best.length ? word : best),
""
);best に入っているのがここまでの答えで、返した値がそのまま次の回の best になります。要素を 1 つずつ受け取りながら、答えを 1 つに畳んでいくので、日本語では畳み込みとも呼びます。配列を 1 周するだけなので、要素が 2 倍になれば時間も 2 倍、という素直な増え方をします。
for との違いは、途中の状態に名前をつけなくてよいことです。longest という変数が、書き換わる箱ではなく、確定した 1 つの結果になります。
畳んだ先は数値でなくても構いません。答えを辞書にすれば「著者ごとの冊数」のような集計になり、答えを配列にすれば並べ替えや詰め替えになります。「1 つの値にまとめる」の 1 つは、1 個の数字という意味ではなく、1 個の入れ物という意味です。
初期値は、空のときの答え
初期値を何にするか迷ったら、「空の配列を渡されたら何を返してほしいか」を考えてください。一番長い単語なら空文字列、合計なら 0、つなげた文字列なら空文字列です。ここが決まると、中の関数も自然に決まります。
JavaScript の reduce は第 2 引数を省くと先頭の要素を初期値として使うので、空の配列を渡した瞬間に例外になります。空が来る可能性があるなら、必ず書いてください。Python の functools.reduce も同じで、初期値を渡さないと空のときに例外になります。
覚え方 ... 初期値は「まだ 1 つも見ていないときの答え」。先にこれを決めると、残りは 1 行で済む。
値が育ちすぎることがある
足し算と違い、掛け算のように 1 要素ごとに値が跳ね上がる集計では、桁あふれに気をつけてください。Python の整数には桁の上限がありませんが、Java の int や Go の int32 は上限を超えた時点で値が壊れ、例外も出ないまま間違った答えが返ります。入力の大きさが読めないときは、64 ビットの整数や多倍長の型を選びます。
要件
- 配列の全要素の積を返すこと
- 空配列の場合は 1 を返すこと (乗法の単位元)
- 戻り値は整数 (int) で返す
入出力例
product([1,2,3,4]) → 24
product([2,5]) → 10
product([7]) → 7
product([1,1,1,1]) → 1
product([3,0,5]) → 0
product([-2,3]) → -6