reduce(),map(),filter()

JS

reduce()

reduce()を検索すると出てくるのが、

array.reduce(callback[, initialValue])

でもこれを読んでもいまいちピンとこない。

角カッコでいきなりカンマは省略が可能だということを表しているそう。

だからinitalValueつまり初期値を省略可能ということ。

callback関数の引数は以下の4つ

  • accumulater //前回の呼び出しの戻り値または初期値(必須)
  • currentValue//現在の要素の値(必須)
  • currentIndex//現在の要素のインデックス(省略可能)
  • array//reduce()が呼び出された配列(省略可能)

reduce()の役割

reduce()は、配列の各要素に対して指定された関数を適用し、単一の値に集約します。

reduce()の役割は、

  • 要素の合計や計算
  • 要素のフィルタリング
  • 要素のマッピング
  • オブジェクトの生成
  • 要素の削除

などがあります。

要素の合計や計算

reduce()の合計について。

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, 0);
console.log(sum); // 15

どういう計算をしているかというと、

0(初期値)+1(現在の値)

1(前回の呼び出しの戻り値) + 2(現在の値)

3(前回の呼び出しの戻り値) + 3(現在の値)

6(前回の呼び出しの戻り値) + 4(現在の値)

10(前回の呼び出しの戻り値) + 5(現在の値)

で結果15になり、1+2+3+4+5=15と一致します。

平均を求める

平均を求めるときもreduce()メソッドを使えます。

const numbers = [1, 2, 3, 4, 5];

// 配列の合計を計算する
const sum = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, 0);

// 配列の要素数を数える
const count = numbers.length;

// 平均を計算する
const average = sum / count;

console.log(average); // 3

15÷5=3で正解ですね。

要素のフィルタリング

let numbers = [1, 2, 3, 4, 5];

// 偶数のみをフィルタリング
let evenNumbers = numbers.reduce((acc, current) => {
    if (current % 2 === 0) {
        acc.push(current);
    }
    return acc;
}, []);
console.log(evenNumbers); // [2, 4]

filter()メソッドと用途が同じですが、filter()の方がシンプルです。

要素のマッピング

let numbers = [1, 2, 3, 4, 5];

// 各要素を2倍にマッピング
let doubledNumbers = numbers.reduce((acc, current) => {
    acc.push(current * 2);
    return acc;
}, []);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]

オブジェクトの生成

let numbers = [1, 2, 3, 4, 5];

// 各要素をキーとし、値をその要素の2倍としたオブジェクトを生成
let doubledObject = numbers.reduce((acc, current) => {
    acc[current] = current * 2;
    return acc;
}, {});
console.log(doubledObject); // { 1: 2, 2: 4, 3: 6, 4: 8, 5: 10 }
const names = ["sadako", "suzuko"];
const scores = ["40", "50"];

const combinedData = names.reduce((result, name, index) => {
result[name] = scores[index];
return result;
}, {});

console.log(combinedData);

{ sadako: "40", suzuko: "50"}
  • 各イテレーションで、result[name] = scores[index] の形式でオブジェクトにプロパティが追加されます。
  • 最初のイテレーションでは name"sadako"index0
    したがって、result["sadako"] = scores[0] となり、result オブジェクトは { sadako: "40" }
  • 次のイテレーションでは name"suzuko"index1
    したがって、result["suzuko"] = scores[1] となり、result オブジェクトは { sadako: "40", suzuko: "50" } になります。

こういう使い方ができるとmap()メソッドとどう違うのかと気になってきます。

違いは下のmap()のところをご覧ください。

条件に基づいて要素をグループ化する

function groupByPrefectures(people) {
    return people.reduce(function(result, person) {
        // personから都道府県を取得
        let prefecture = person.prefecture;
        
        // 都道府県ごとのグループが既に存在するかチェック
        if (!result[prefecture]) {
            // グループが存在しない場合、新しい配列を作成して初期化
            result[prefecture] = [];
        }
        
        // 人々を都道府県ごとのグループに追加
        result[prefecture].push(person);
        
        // 更新されたグループを返す
        return result;
    }, {});
}
function groupByPrefectures(people) {
    return people.reduce(function(result, person) {
        // personから都道府県を取得
        let prefecture = person.prefecture;
        
        // 都道府県ごとのグループが既に存在するかチェック
        if (!result[prefecture]) {
            // グループが存在しない場合、新しい配列を作成して初期化
            result[prefecture] = [];
        }
        
        // 人々を都道府県ごとのグループに追加
        result[prefecture].push(person);
        
        // 更新されたグループを返す
        return result;
    }, {});
}
let people = [
    { name: 'Alice', age: 25, prefecture: 'Tokyo' },
    { name: 'Bob', age: 30, prefecture: 'Osaka' },
    { name: 'Charlie', age: 35, prefecture: 'Tokyo' },
    { name: 'David', age: 40, prefecture: 'Osaka' },
    { name: 'Eve', age: 45, prefecture: 'Kyoto' }
];
let groupedPeople = groupByPrefectures(people);
console.log(groupedPeople);
//出力結果
{
    Tokyo: [
        { name: 'Alice', age: 25, prefecture: 'Tokyo' },
        { name: 'Charlie', age: 35, prefecture: 'Tokyo' }
    ],
    Osaka: [
        { name: 'Bob', age: 30, prefecture: 'Osaka' },
        { name: 'David', age: 40, prefecture: 'Osaka' }
    ],
    Kyoto: [
        { name: 'Eve', age: 45, prefecture: 'Kyoto' }
    ]
}

要素の削除

let numbers = [1, 2, 3, 4, 5];

// 特定の条件を満たす要素を削除
let filteredNumbers = numbers.reduce((acc, current) => {
    if (current !== 3) {
        acc.push(current);
    }
    return acc;
}, []);
console.log(filteredNumbers); // [1, 2, 4, 5]

map()

reduce()のオブジェクトの生成と同じ例を使うと違いがよく分かります。

const names = ["sadako", "suzuko"];
const scores = ["40", "50"];

const mappedData = names.map((name, index) => {
  return { [name]: scores[index] };
});

console.log(mappedData);
//  [{ sadako: "40" }, { suzuko: "50" }]

まさにconsole.logで出された結果がreduce()とmap()の違いですね。

reduce()は単一の値にまとめるので、最終的には1つのオブジェクトが生成されます。

一方map()は新しい配列を生成します。(元配列はそのまま、新しい配列を作る)

array.map(function(element, index, array) {
  // element: 現在の要素
  // index: 現在の要素のインデックス
  // array: 元の配列
  return newValue; // 新しい配列の要素として入る
});

使用例

例1:文字列を大文字にする

const fruits = ["apple", "banana", "orange"];
const upper = fruits.map(f => f.toUpperCase());
console.log(upper); 
// → ["APPLE", "BANANA", "ORANGE"]

例2:オブジェクトの配列から特定の値だけ取り出す

const users = [
  { name: "太郎", age: 20 },
  { name: "花子", age: 25 }
];

const names = users.map(u => u.name);
console.log(names);
// → ["太郎", "花子"]

例3: テンプレートリテラル

function escapeHTML(str) {
  return str
    .replace(/&/g, "&")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

const html = `
  <table border="1">
    <tr><th>ID</th><th>ラベル</th><th>値</th></tr>
    ${fields.map(({ id, label, value }) => `
      <tr>
        <td>${id}</td>
        <td>${escapeHTML(label)}</td>
        <td>${escapeHTML(value)}</td>
      </tr>
    `).join("")}
  </table>
`;

document.getElementById("results").innerHTML = html;

join()

配列の各要素を 指定した文字でつなぐ

引数が "," なら カンマで連結

const arr = ["apple", "banana", "orange"];
console.log(arr.join(",")); // "apple,banana,orange"

引数が " " なら 空白で連結

const arr = ["apple", "banana", "orange"];

console.log(arr.join(" "));  // "apple banana orange" ← 空白で区切る
console.log(arr.join(""));   // "applebananaorange"  ← 何も入れずに繋ぐ

引数が "" なら 何も入れずに連結

fields.map(({id,label,value}) => `
  <tr><td>${id}</td><td>${label}</td><td>${value}</td></tr>
`).join("")

配列の要素間にカンマや余計な文字が入らず、きれいに連続した HTML 文字列になる

const baseUrl = "webapi/search?dbid=1";

// 検索ボタンやセレクトボックスの onchange で呼ぶ関数
async function fetchData() {
  const select1Value = document.querySelector("#select1").value;
  const select2Value = document.querySelector("#select2").value;

  // 配列に条件を追加
  const params = [];
  if (select1Value) params.push(`keyword=${encodeURIComponent(select1Value)}`);
  if (select2Value) params.push(`a=${encodeURIComponent(select2Value)}`);

  // 配列を & で結合して URL を生成
  const apiUrl = baseUrl + (params.length ? "&" + params.join("&") : "");
  console.log("API URL:", apiUrl);

new Map()

Map は「キーと値のペア」を扱うのに強い構造です。
例えば「id をキーにして高速に検索したい」場合に便利。

const fields = [
  { id: 0, value: "入力値A", label: "名前" },
  { id: 1, value: "入力値B", label: "住所" },
  { id: 2, value: "入力値C", label: "電話" }
];

// id をキーにした Map を作成
const fieldMap = new Map(fields.map(f => [f.id, f]));

// id=1 を高速に取得
const { label, value } = fieldMap.get(1);
console.log(label, value); // 住所 入力値B

ついでにもう一つ

filter()

filter()メソッドは元の配列から条件に合致する要素のみを抽出します。

元の配列は変更されません。

上の例でいくと、

const names = ["sadako", "suzuko", "taro"];
const scores = ["40", "50", "60"];

// スコアが50以上の生徒のみを抽出する
const filteredData = names.filter((name, index) => {
  return number(scores[index]) >= 50;
});

console.log(filteredData);
// ["suzuko", "taro"]

find → 配列から 最初の1件だけ を返す

filter → 条件に合う すべての要素を配列で返す