reduce() は配列を1つの値にまとめます。初期値を省いたときの落とし穴、空配列で例外になる条件、合計・グループ化の書き方、いつ使わないほうがよいかまでまとめます。
reduce() は、配列を1つの値にまとめます。
累積値を持ち回りながら、要素を1つずつ畳み込んでいきます。
example.js
const nums = [1, 2, 3, 4];
const total = nums.reduce((sum, n) => sum + n, 0);
console.log(total);
出力書き換えて実行できます
10
example.js
const nums = [1, 2, 3];
const total = nums.reduce((sum, n, i) => {
console.log(`${i}回目: 累積 ${sum} + 要素 ${n} = ${sum + n}`);
return sum + n;
}, 0);
console.log('結果:', total);
出力書き換えて実行できます
0回目: 累積 0 + 要素 1 = 1
1回目: 累積 1 + 要素 2 = 3
2回目: 累積 3 + 要素 3 = 6
結果: 6
第2引数の 0 が初期値です。省略できますが、省略しないでください(理由は後述)。
上のコードは書き換えて実行できます。sum + n を sum * n、0 を 1 にしてみてください。
引数
|
中身 |
| 第1引数 |
コールバック関数 (累積値, 要素, 添字, 配列) => 次の累積値 |
| 第2引数 |
初期値(省略可。省略しないほうがよい) |
| 戻り値 |
最後の累積値。配列とは限らない |
| 元の配列 |
変わらない |
何が起きているか
累積値がどう渡っていくかを見ます。
javascript
const nums = [1, 2, 3];
const total = nums.reduce((sum, n) => {
sum + n;
}, 0);
console.log(total);
undefined
example.js
const nums = [1, 2, 3];
console.log(nums.reduce((a, b) => a + b));
console.log(nums.reduce((a, b) => a + b, 0));
出力書き換えて実行できます
6
6
返した値が、次の回の第1引数になります。 ここが map() との一番の違いです。
return を書き忘れると、次の回の累積値が undefined になります。
javascript
const items = [
{ price: 100 },
{ price: 200 },
];
console.log(items.reduce((sum, item) => sum + item.price));
[object Object]200
example.js
const items = [
{ price: 100 },
{ price: 200 },
];
console.log(items.reduce((sum, item) => sum + item.price, 0));
出力書き換えて実行できます
300
初期値を省くと危ない
初期値を省くと、先頭の要素がそのまま最初の累積値になり、2番目から回り始めます。
javascript
console.log([].reduce((a, b) => a + b));
TypeError: Reduce of empty array with no initial value
example.js
console.log([].reduce((a, b) => a + b, 0));
console.log([].reduce((a, b) => a + b, []));
出力書き換えて実行できます
0
[]
数の合計なら同じ結果です。ですが型が違うと壊れます。
example.js
const scores = [72, 95, 61, 88];
console.log(scores.reduce((sum, n) => sum + n, 0));
console.log(scores.reduce((max, n) => (n > max ? n : max), -Infinity));
console.log(Math.max(...scores));
出力書き換えて実行できます
316
95
95
example.js
const votes = ['りんご', 'みかん', 'りんご', 'ぶどう', 'りんご'];
const count = votes.reduce((acc, v) => {
acc[v] = (acc[v] ?? 0) + 1;
return acc;
}, {});
console.log(count);
出力書き換えて実行できます
{ 'りんご': 3, 'みかん': 1, 'ぶどう': 1 }
最初の累積値がオブジェクトそのものになり、文字列として繋がりました。
初期値 0 を書けば直ります。
example.js
const users = [
{ name: 'あかり', team: 'A' },
{ name: 'ゆうと', team: 'B' },
{ name: 'はると', team: 'A' },
];
const byTeam = users.reduce((acc, u) => {
(acc[u.team] ??= []).push(u.name);
return acc;
}, {});
console.log(byTeam);
出力書き換えて実行できます
{ A: [ 'あかり', 'はると' ], B: [ 'ゆうと' ] }
example.js
const users = [
{ name: 'あかり', team: 'A' },
{ name: 'ゆうと', team: 'B' },
{ name: 'はると', team: 'A' },
];
console.log(Object.groupBy(users, u => u.team).A.map(u => u.name));
出力書き換えて実行できます
[ 'あかり', 'はると' ]
空配列では例外になる
初期値が無く、要素も無いと、返すものが決まりません。 仕様で TypeError と定められています。
javascript
const nums = [1, 2, 3, 4];
const doubled = nums.reduce((acc, n) => {
acc.push(n * 2);
return acc;
}, []);
console.log(doubled);
[ 2, 4, 6, 8 ]
example.js
const nums = [1, 2, 3, 4];
console.log(nums.map(n => n * 2));
出力書き換えて実行できます
[ 2, 4, 6, 8 ]
初期値があれば、そのまま返ります。
javascript
const votes = ['a', 'b', 'a'];
const count = votes.reduce((acc, v) => ({ ...acc, [v]: (acc[v] ?? 0) + 1 }), {});
console.log(count);
{ a: 2, b: 1 }
example.js
const sparse = [1, , 3];
let calls = 0;
const total = sparse.reduce((sum, n) => { calls++; return sum + n; }, 0);
console.log(total);
console.log('呼ばれた回数:', calls);
出力書き換えて実行できます
4
呼ばれた回数: 2
空配列は普通に起こります(filter() の結果など)。
初期値は必ず書いてください。
よく使う形
合計・最大
example.js
const parts = ['a', 'b', 'c'];
console.log(parts.reduce((acc, s) => acc + s, ''));
console.log(parts.reduceRight((acc, s) => acc + s, ''));
出力書き換えて実行できます
abc
cba
最大値は Math.max(...arr) のほうが短く読めます。ただし要素が数万を超えると RangeError になるので、そのときは reduce にしてください。
合計まわりの落とし穴(小数・空配列・文字列が混ざる)は
配列の合計を出す にまとめてあります。
数える
初期値が {} なので、戻り値は配列ではなくオブジェクトです。
グループ化
Object.groupBy() を使うともっと短く書けます(ES2024。Node 21 / Chrome 117 以降)。
使わないほうがよいとき
reduce() は何でも書けてしまいます。書けることと、読みやすいことは別です。
これは map() そのものです。短く、意図もはっきりします。
reduce() を選ぶのは、出てくる形が元と違うときだけにしてください。
配列が配列のまま出てくるなら、たいてい別のメソッドが合っています。
累積値を毎回作り直さない
短く書きたくてこう書くと、要素の数だけオブジェクトを作り直します。
結果は合っています。ですが { ...acc } は毎回まるごと複製しています。
要素が増えると急に遅くなります。書き換えて返してください(上の「数える」の形)。
穴のある配列では呼ばれない
疎な配列の穴では、コールバック関数は呼ばれません。
map() と同じ扱いです。find() は穴でも呼ばれるので、そこだけ違います。
右から畳み込む
reduceRight() は末尾から回ります。順序が結果に影響するときだけ使います。