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 mappedData = names.map((name, index) => {
  return { [name]: scores[index] };
});

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

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

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

一方map()は新しい配列を生成します。

ついでにもう一つ

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"]