ウェブエンジニア問題集
配列メソッド 1

配列の変換メソッド — map・filter・reduce

6
この章の目次開く

mapfilterreduce は、配列メソッドの中でも特に使用頻度が高い3つです。 それぞれの役割は、次のように覚えると整理しやすくなります。

メソッド役割戻り値
map各要素を変換する新しい配列
filter条件に合う要素だけ残す新しい配列
reduce配列全体を1つの値にまとめる任意の値

map — 各要素を変換して新しい配列を作る

mapが変換結果を新しい配列として返すイメージ

map は配列の各要素にコールバック関数を適用し、その戻り値で構成された新しい配列を返します。 元の配列は変更されません(非破壊)。

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
 
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5](元の配列はそのまま)
js

コールバックの引数

const result = ['a', 'b', 'c'].map((element, index, array) => {
  // element: 現在の要素("a", "b", "c")
  // index:   現在のインデックス(0, 1, 2)
  // array:   元の配列自体
  return `${index}: ${element}`;
});
// ["0: a", "1: b", "2: c"]
js

実務パターン: APIレスポンスの整形

const users = [
  { id: 1, first_name: '太郎', last_name: '田中' },
  { id: 2, first_name: '花子', last_name: '佐藤' },
];
 
const displayUsers = users.map((user) => ({
  id: user.id,
  fullName: `${user.last_name} ${user.first_name}`,
}));
// [{ id: 1, fullName: "田中 太郎" }, { id: 2, fullName: "佐藤 花子" }]
js

map を使うべきでない場面

map は「変換した新しい配列が欲しいとき」に使います。 戻り値を使わずに副作用(ログ出力、DOM操作など)だけが目的なら forEach を使ってください。

// ❌ mapの戻り値を捨てている
users.map((user) => {
  console.log(user.name);
});
 
// ⭕
users.forEach((user) => {
  console.log(user.name);
});
js

filter — 条件に合う要素だけを残す

filter はコールバックが true を返した要素だけで構成された新しい配列を返します。 元の配列は変更されません。

const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter((n) => n % 2 === 0);
 
console.log(evens); // [2, 4, 6]
js

実務パターン: 検索フィルタリング

const products = [
  { name: 'ノートPC', price: 120000, inStock: true },
  { name: 'マウス', price: 3000, inStock: true },
  { name: 'キーボード', price: 15000, inStock: false },
  { name: 'モニター', price: 45000, inStock: true },
];
 
const result = products.filter((p) => p.inStock && p.price >= 10000);
// [{ name: "ノートPC", ... }, { name: "モニター", ... }]
js

filter + map のチェーン

filter で絞り込んでから map で変換する、というチェーンは実務で非常によく書きます。

const inStockNames = products.filter((p) => p.inStock).map((p) => p.name);
// ["ノートPC", "マウス", "モニター"]
js

該当なしの場合

条件に合う要素がなければ空配列 [] が返ります。nullundefined ではありません。

const result = [1, 2, 3].filter((n) => n > 10);
console.log(result); // []
console.log(result.length); // 0
js

reduce — 配列を1つの値にまとめる

reduce は配列の各要素を順に処理し、最終的に1つの値に集約します。 合計値、オブジェクト、別の配列など、あらゆる形に集約できる汎用性の高いメソッドです。

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, current) => {
  return accumulator + current;
}, 0);
// 15
js
学習者学習者

reduce だけ急に難しい…!accumulator って何者…?

先生先生

accumulator(アキュムレータ)は「途中経過を入れておく箱」。要素を1つ処理するたびに箱の中身を更新して、最後にその箱を返す——そうイメージすると一気にわかりやすくなるよ。

引数の構造

array.reduce((accumulator, currentValue, index, array) => {
  // accumulator: 前回のコールバックの戻り値(初回は初期値)
  // currentValue: 現在の要素
  return nextAccumulator;
}, initialValue);
//  ^^^^^^^^^^^^^ 第2引数: 初期値
js

初期値は必ず指定する

初期値を省略すると、配列の最初の要素が初期値として使われます。 空配列に対して初期値なしで reduce を呼ぶと TypeError になるため、初期値は常に指定するのが安全です。

// ❌ 空配列でエラー
[].reduce((acc, cur) => acc + cur);
 
// ⭕ 初期値を指定すれば空配列でも安全
[].reduce((acc, cur) => acc + cur, 0); // 0
js

配列の最大値を求める

合計以外にも、要素同士を比較して1つの値に絞り込む処理にも reduce が使えます。

const scores = [72, 95, 58, 88, 100];
const max = scores.reduce((acc, cur) => Math.max(acc, cur), -Infinity);
// 100
js

初期値に -Infinity を指定することで、どんな数値と比較しても最初の要素が必ず採用されます。

実務パターン: グルーピング

const orders = [
  { product: 'PC', category: 'electronics' },
  { product: 'シャツ', category: 'clothing' },
  { product: 'マウス', category: 'electronics' },
  { product: 'パンツ', category: 'clothing' },
];
 
const grouped = orders.reduce((acc, order) => {
  const key = order.category;
  if (!acc[key]) {
    acc[key] = [];
  }
  acc[key].push(order);
  return acc;
}, {});
js

なお、このグルーピングは ES2024 で追加された Object.groupBy でより簡潔に書けるようになりました。

const grouped = Object.groupBy(orders, (order) => order.category);
js

reduce を使いすぎない

reduce は汎用的すぎるがゆえに、何をしているか読みにくくなりがちです。 map + filter で書ける処理を無理に reduce で書く必要はありません。

// ❌ reduceで書いているが、やっていることはfilter + map
const result = numbers.reduce((acc, n) => {
  if (n > 3) acc.push(n * 2);
  return acc;
}, []);
 
// ⭕ 意図が明確
const result = numbers.filter((n) => n > 3).map((n) => n * 2);
js

メソッドチェーンの実践パターン

配列メソッドを連鎖させるメソッドチェーンは、実務で最もよく書くパターンの一つです。

const transactions = [
  { id: 1, type: 'income', amount: 50000, date: '2024-06' },
  { id: 2, type: 'expense', amount: 3000, date: '2024-06' },
  { id: 3, type: 'expense', amount: 15000, date: '2024-07' },
  { id: 4, type: 'income', amount: 80000, date: '2024-07' },
  { id: 5, type: 'expense', amount: 5000, date: '2024-06' },
];
 
const juneExpenses = transactions
  .filter((t) => t.type === 'expense')
  .filter((t) => t.date === '2024-06')
  .reduce((sum, t) => sum + t.amount, 0);
// 8000
js

チェーンは上から下に「絞り込み → 変換 → 集約」の順で読めるのが理想です。

途中で結果を確認したいとき

チェーンの途中で console.log を挟みたい場合、一時変数に切り出すと確認しやすくなります。

const expenses = transactions.filter((t) => t.type === 'expense');
console.log('expenses:', expenses);
const total = expenses.reduce((sum, t) => sum + t.amount, 0);
js

ちゃんと使うためのポイント

  • map は変換した新しい配列が欲しいときに使う
  • filter は条件に合う要素だけを残したいときに使う
  • reduce は合計、グルーピング、オブジェクトへの変換などの集約で使う
  • reduce で何でも書けるが、読みやすさでは filter + map の方がよいことも多い

次は、条件に合う要素を探したり判定したりする 配列の検索・判定メソッド を見ていきます。

参考リンク