JavaScript / LESSON 09 / JS09

JavaScript:配列の高階関数(map / filter / reduce)

1学習の目的

2基礎解説

やりたいこと書き方戻り値
全部を変換nums.map(n => n * 2)同じ数の新しい配列
条件で絞るnums.filter(n => n > 5)減った新しい配列
1つにまとめるnums.reduce((s, n) => s + n, 0)値ひとつ
1件だけ探すusers.find(u => u.id === 3)要素 or undefined
✅ 覚えるべき重要ポイント
  1. map は要素数が変わらず、filter は減る。「変換したい」のか「絞りたい」のかで選ぶ。両方したいときは .filter(…).map(…) とつなぐ。
  2. { } を書いたら return が必須map(n => { n * 2 })[undefined, undefined] になる。式1つなら { } を書かないほうが安全。
  3. reduce は「積み上げる箱(第2引数)」を必ず書く。reduce((合計, 要素) => …, 0) の 0 が初期値で、省略すると空配列でエラーになる。
  4. これらは元の配列を変更しない。新しい配列が返るので、JS06の sort と違ってコピーを気にしなくてよい。
  5. forEach は戻り値が undefined。結果が欲しいなら map、画面出力など「実行するだけ」なら forEach と使い分ける。

現場使用例:APIの応答から必要な項目だけ抜き出す、条件に合う行だけ表示、合計金額の算出、フォーム入力を一括で数値に変換。

const nums = [1, 2, 3, 4, 5, 6];

console.log(nums.map(n => n * 2));       // [2,4,6,8,10,12]
console.log(nums.filter(n => n % 2 === 0)); // [2,4,6]
console.log(nums.reduce((sum, n) => sum + n, 0)); // 21

// つないで書ける(絞ってから変換)
console.log(nums.filter(n => n > 3).map(n => n * 10)); // [40,50,60]

// オブジェクトの配列から必要な項目だけ取り出す
const users = [
  { name: "田中", score: 82 },
  { name: "鈴木", score: 45 },
];

const passed = users.filter(u => u.score >= 60).map(u => u.name);
console.log(passed);                     // ['田中']

// よくある間違い:{ } を書いて return を忘れる
console.log(nums.map(n => { n * 2 }));   // [undefined, ...]

3基本ドリル(10問)

JS09-D01 nums = [1, 2, 3, 4] の各要素を2倍にした配列を map で作って出力せよ。 コーディング★☆☆無料
期待される結果

出力 == [ 2, 4, 6, 8 ]

ヒント

nums.map(n => n * 2) と書く。

模範解答
const nums = [1, 2, 3, 4];
console.log(nums.map(n => n * 2));
解説

map の中の関数は要素1つを受け取って、変換後の値を返す。for ループ3行が1行になる。

JS09-D02 [1, 2, 3].map(n => n * 2) の結果の要素数はいくつか。
A. 1 B. 2 C. 3 D. 6
選択★☆☆無料
期待される結果

解答 == C

ヒント

map は変換であって絞り込みではない。

模範解答
C
解説

map は要素数が変わらない。中身が変わるだけ。減らしたいときは filter を使う。

JS09-D03 nums = [3, 8, 1, 10, 6] から 5より大きい要素だけを filter で取り出して出力せよ。 コーディング★☆☆無料
期待される結果

出力 == [ 8, 10, 6 ]

ヒント

nums.filter(n => n > 5) と書く。条件が true の要素だけ残る。

模範解答
const nums = [3, 8, 1, 10, 6];
console.log(nums.filter(n => n > 5));
解説

filter の中の関数はtrue / false を返す。true になった要素だけが新しい配列に入り、元の並び順は保たれる。

JS09-D04 配列の合計を求めるコードを完成させよ。 穴埋め★☆☆無料
コード
const nums = [10, 20, 30];
console.log(nums.____((sum, n) => sum + n, 0));
期待される結果

出力 == 60

ヒント

「集約する」という意味の英単語。第2引数が初期値。

模範解答
reduce
解説

第1引数 sum が積み上げていく箱、第2引数 n が今回の要素。最後の 0 が箱の初期値。

JS09-D05 [1, 2].map(n => { n * 2 }) の結果はどれか。
A. [2, 4] B. [undefined, undefined] C. [] D. エラー
選択★☆☆無料
期待される結果

解答 == B

ヒント

波カッコを書いた場合、値を返すには何が必要か。

模範解答
B
解説

{ } を書いたら return が必須。この書き忘れは頻出のバグで、結果が undefined だらけになったらまずここを疑う。

JS09-D06 nums = [1, 2, 3, 4, 5, 6] から偶数だけを取り出し、それぞれを2乗した配列を出力せよ(filter と map をつなぐこと)。 コーディング★★☆無料
期待される結果

出力 == [ 4, 16, 36 ]

ヒント

nums.filter(...).map(...) のように続けて書ける。

模範解答
const nums = [1, 2, 3, 4, 5, 6];
console.log(nums.filter(n => n % 2 === 0).map(n => n ** 2));
解説

絞ってから変換するのが基本の順番。先に map すると不要な要素まで計算することになり無駄が出る。

JS09-D07 点数の配列を「合格」「不合格」の文字列配列に変換するコードを完成させよ(60点以上が合格)。 穴埋め★★☆無料
コード
const scores = [82, 45, 91];
console.log(scores.map(s => ____));
期待される結果

出力 == [ '合格', '不合格', '合格' ]

ヒント

三項演算子を使う。条件 ? 真の値 : 偽の値。

模範解答
s >= 60 ? "合格" : "不合格"
解説

要素数は減らずに中身だけが変わる。D03の filter(要素が減る)との違いを見比べてほしい。

JS09-D08 配列から条件に合う最初の1件を取り出すメソッドはどれか。
A. filter B. find C. map D. some
選択★★☆無料
期待される結果

解答 == B

ヒント

1件だけ欲しいときのメソッド。

模範解答
B
解説

filter は配列を返し、find は要素そのものを返す。見つからない場合 find は undefined。1件だけ欲しいのに filter を使うと [0] を付ける必要が出て読みにくい。

JS09-D09 users = [{ name: "田中", score: 82 }, { name: "鈴木", score: 45 }, { name: "高橋", score: 91 }] から60点以上の人の名前だけの配列を出力せよ。 コーディング★★☆無料
期待される結果

出力 == [ '田中', '高橋' ]

ヒント

filter で絞ってから map で名前を取り出す。

模範解答
const users = [
  { name: "田中", score: 82 },
  { name: "鈴木", score: 45 },
  { name: "高橋", score: 91 },
];

console.log(users.filter(u => u.score >= 60).map(u => u.name));
解説

オブジェクトの配列を絞って必要な項目だけ抜く——APIの応答を扱うときに最も頻繁に書く形。実務コードの定番中の定番。

JS09-D10 raw = [" 100 ", "250", " 80 "] を reduce で合計せよ(文字列なので数値に変換すること)。 コーディング★★☆無料
期待される結果

出力 == 430

ヒント

reduce の中で Number() に通す。初期値は 0。

模範解答
const raw = [" 100 ", "250", " 80 "];
console.log(raw.reduce((sum, r) => sum + Number(r), 0));
解説

変換と集計を同時に行えるのが reduce の強み。.map(Number).reduce(…) と2段階に分けても書けるが、1回で済むならこちらが速い。

4実践シナリオ(5問)

JS09-S01 【税込価格リスト】prices = [1200, 850, 3400] から税込価格(10%・Math.round)の配列を map で作り、「1,200円 → 1,320円」の形式で3行出力せよ。 コーディング★★☆無料
期待される結果

出力3行が完全一致

ヒント

先に taxed 配列を作ってから、prices を map((p, i) => ...) で回すと i で対応が取れる。

模範解答
const prices = [1200, 850, 3400];

const taxed = prices.map(p => Math.round(p * 1.1));

prices.forEach((p, i) => {
  console.log(`${p.toLocaleString()}円 → ${taxed[i].toLocaleString()}円`);
});
解説

map と forEach の使い分けがポイント。データを作るのが map、画面に出すだけなのが forEach。map の第2引数で番号(index)も受け取れる。

JS09-S02 【合格者の抽出】scores = [82, 45, 91, 67, 38, 76] から60点以上を filter で抽出し、「合格者:[ 82, 91, 67, 76 ]」「4人 / 平均79.0点」の2行を出力せよ。 コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

passed 配列を作り、人数は length、平均は reduce で合計してから割り、toFixed(1) で小数1桁にする。

模範解答
const scores = [82, 45, 91, 67, 38, 76];

const passed = scores.filter(s => s >= 60);
const average = passed.reduce((sum, s) => sum + s, 0) / passed.length;

console.log(`合格者:${JSON.stringify(passed)}`);
console.log(`${passed.length}人 / 平均${average.toFixed(1)}点`);
解説

JS05のS03ではループとカウンタで書いた処理がfilter 1行+reduce 1行になった。抽出した配列があれば件数も平均もすぐ出せる。

JS09-S03 【1件を探す】users = [{ id: 1, name: "田中" }, { id: 2, name: "鈴木" }, { id: 3, name: "高橋" }] から、find を使って次の2行を出力せよ。
「id=2 → 鈴木」
「id=99 → 見つかりません」
コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

find は見つからないと undefined を返す。?. と ?? を組み合わせると安全に書ける(JS07)。

模範解答
const users = [
  { id: 1, name: "田中" },
  { id: 2, name: "鈴木" },
  { id: 3, name: "高橋" },
];

const found = users.find(u => u.id === 2);
const notFound = users.find(u => u.id === 99);

console.log(`id=2 → ${found?.name ?? "見つかりません"}`);
console.log(`id=99 → ${notFound?.name ?? "見つかりません"}`);
解説

find の戻り値は undefined になりうるので、そのまま .name を付けるとエラーで止まる。JS07のオプショナルチェーン ?. がここで必須になる。

JS09-S04 【reduce で集計】sales = [["A", 1200], ["B", 800], ["A", 500], ["C", 300], ["B", 1000]] から商品ごとの合計を reduce で1回で求め、「A:1,700円」の形式で出力し、最後に「合計:3,800円」と出力せよ。 コーディング★★★無料
期待される結果

商品別3行+最終行 合計:3,800円

ヒント

reduce の初期値を {} にし、(acc, [name, amount]) => { acc[name] = (acc[name] || 0) + amount; return acc; } とする。{ } を書いたので return を忘れないこと。

模範解答
const sales = [["A", 1200], ["B", 800], ["A", 500], ["C", 300], ["B", 1000]];

const totals = sales.reduce((acc, [name, amount]) => {
  acc[name] = (acc[name] || 0) + amount;
  return acc;
}, {});

Object.entries(totals).forEach(([name, amount]) => {
  console.log(`${name}:${amount.toLocaleString()}円`);
});

const sum = Object.values(totals).reduce((s, v) => s + v, 0);
console.log(`合計:${sum.toLocaleString()}円`);
解説

reduce の初期値は数値だけでなくオブジェクトでもよい。JS07で for ループを使って書いた集計が reduce 1つに収まる。ただし return acc; を忘れると次の周回で undefined になるので要注意。

JS09-S05 【ランキング生成】products = [{ name: "ノートPC", sales: 128000 }, { name: "マウス", sales: 6400 }, { name: "モニター", sales: 45000 }, { name: "キーボード", sales: 8500 }] を売上の多い順に並べ、上位3件を「1位 ノートPC:128,000円」の形式で出力せよ。元の配列は変更しないこと コーディング★★★無料
期待される結果

出力3行が完全一致

ヒント

[...products] でコピーしてから sort((a, b) => b.sales - a.sales)、slice(0, 3)、forEach((p, i) => ...) とつなぐ。

模範解答
const products = [
  { name: "ノートPC", sales: 128000 },
  { name: "マウス", sales: 6400 },
  { name: "モニター", sales: 45000 },
  { name: "キーボード", sales: 8500 },
];

[...products]
  .sort((a, b) => b.sales - a.sales)
  .slice(0, 3)
  .forEach((p, i) => {
    console.log(`${i + 1}位 ${p.name}:${p.sales.toLocaleString()}円`);
  });
解説

メソッドを縦に並べて書くと処理の流れが上から読める。コピー → 並べ替え → 切り出し → 出力、という手順がそのままコードの形になっている。sort は元を壊すので [...products] は必須(JS06)。

5仕上げ課題

JS09-FINAL 【受注データ分析レポート】注文データを高階関数で分析し、レポートを出力せよ。

const orders = [
 { item: "ノートPC", price: 128000, qty: 1, dept: "法人" },
 { item: "マウス", price: 3200, qty: 2, dept: "個人" },
 { item: "モニター", price: 45000, qty: 1, dept: "法人" },
 { item: "キーボード", price: 8500, qty: 3, dept: "個人" },
 { item: "USBメモリ", price: 1500, qty: 5, dept: "法人" },
];


仕様(すべて map / filter / reduce / sort を使うこと):
① 各注文の小計(price × qty)を出し、「ノートPC:128,000円」の形式で全件出力
② 総売上を reduce で算出
③ 小計が10,000円以上の注文を filter で数える
④ 部門別の合計を reduce で集計して出力
⑤ 小計の多い順トップ2の商品名を出力

出力仕様(12行):
=== 受注分析 ===
ノートPC:128,000円
マウス:6,400円
モニター:45,000円
キーボード:25,500円
USBメモリ:7,500円
---
総売上:212,400円 / 5件
1万円以上:3件
[部門別]
法人:180,500円
個人:31,900円
---
トップ2:ノートPC、モニター
期待される結果

出力13行が完全一致

ヒント

まず map で { item, dept, subtotal } の配列を作っておくと、以降の集計がすべてこの配列から書ける。部門別は reduce の初期値を {} にする。トップ2は [...lines].sort((a,b) => b.subtotal - a.subtotal).slice(0,2).map(l => l.item).join("、")。

模範解答
const orders = [
  { item: "ノートPC", price: 128000, qty: 1, dept: "法人" },
  { item: "マウス", price: 3200, qty: 2, dept: "個人" },
  { item: "モニター", price: 45000, qty: 1, dept: "法人" },
  { item: "キーボード", price: 8500, qty: 3, dept: "個人" },
  { item: "USBメモリ", price: 1500, qty: 5, dept: "法人" },
];

const lines = orders.map(o => ({
  item: o.item,
  dept: o.dept,
  subtotal: o.price * o.qty,
}));

console.log("=== 受注分析 ===");

lines.forEach(l => {
  console.log(`${l.item}:${l.subtotal.toLocaleString()}円`);
});

const total = lines.reduce((sum, l) => sum + l.subtotal, 0);
const big = lines.filter(l => l.subtotal >= 10000);

const byDept = lines.reduce((acc, l) => {
  acc[l.dept] = (acc[l.dept] || 0) + l.subtotal;
  return acc;
}, {});

const top2 = [...lines]
  .sort((a, b) => b.subtotal - a.subtotal)
  .slice(0, 2)
  .map(l => l.item)
  .join("、");

console.log("---");
console.log(`総売上:${total.toLocaleString()}円 / ${lines.length}件`);
console.log(`1万円以上:${big.length}件`);

console.log("[部門別]");
Object.entries(byDept).forEach(([dept, amount]) => {
  console.log(`${dept}:${amount.toLocaleString()}円`);
});

console.log("---");
console.log(`トップ2:${top2}`);
解説

最初に map で「小計付きの配列」を1本作り、以降のすべての集計をそこから導く——これがこの課題の設計の要。同じ計算を何度も書かずに済み、元データも汚さない。

注目してほしいのは o => ({ item: …, subtotal: … })丸カッコだ。アロー関数でオブジェクトをそのまま返すときは ( ) で包む必要がある。包まないと { } が関数の本体だと解釈され、undefined が返ってしまう。

JS05では for ループで書いていたこのレポートが、map / filter / reduce / sort を並べるだけで書けるようになった。ループ変数も条件分岐も出てこず、「何をしたいか」がそのままコードになっている。これが現代のJavaScriptの標準的な書き方で、実務のコードレビューでもこの形が期待される。

次章ではエラー処理を学ぶ。ここまでのコードは「データが必ず正しい」前提だったが、現実のデータは壊れている。