6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptのreduce関数を完全に理解する。

Last updated at Posted at 2024-01-28

始めに

本記事では、reduce関数の処理の流れを簡潔に解説し、
基本的な使い方を紹介することで、初心者でもreduce関数の使い方を理解し、活用できるようになることを目指します。

reduce関数の基本

reduce関数は、配列の各要素に対して指定されたリデューサー関数を実行し、
その結果を単一の値に集約(累積)します。
このプロセスは、配列の最初から最後まで順に進みます。

reduce関数の引数

reduce関数には、主に2つの引数があります

  1. リデューサー関数これはreduceが実行するコールバック関数で、以下の4つの引数を取ることができます。
    1. accumulator(累積値) : reduce関数は配列の各要素に対して指定された関数を実行し、単一の出力値(累積値)を生成します。

    2. currentValue (現在の値):処理中の配列の要素です。

    3. currentIndex (現在のインデックス):処理中の配列の要素のインデックスです。(オプショナル)。

    4. array (配列): reduceが呼び出された配列です(オプショナル)。

  2. 初期値:accumulatorの最初の値です。この引数はオプショナルですが、設定することが推奨されます。

reduceの呼び出し方

reduce関数は以下の形式で使用します:

array.reduce(リデューサー関数, 初期値);

具体的な例

配列内の全ての数値を合計する例を考えてみましょう。

const numbers = [1, 2, 3, 4];
// 初期値として 0 を設定します。
const sum = numbers.reduce((accumulator, currentValue, currentIndex, originalArray) => {;
  return accumulator + currentValue;
}, 0);
console.log(sum) //出力10

この例では、reduce関数は配列numbersの各要素に対して、
初期値0から始まる累積値に現在の要素を加算する操作を行います。

ステップバイステップで処理を追ってみる。

1. 初期状態: accumulator = 0, currentValue = 1 (配列の最初の要素)
2. 第1ステップ: accumulator = 0 + 1 = 1, 次のcurrentValue = 2
3. 第2ステップ: accumulator = 1 + 2 = 3, 次のcurrentValue = 3
4. 第3ステップ: accumulator = 3 + 3 = 6, 次のcurrentValue = 4
5. 最終ステップ: accumulator = 6 + 4 = 10, 処理を終了し、sum = 10を返します。

このプロセスを視覚化するために、各ステップでのaccumulatorの値の変化を追い、currentValueがどのようにaccumulatorに加えられていくかをイメージしてみてください。

reduce関数は、このようにして配列の全ての要素を左から右へと処理し、最終的に単一の値を生成します。

使用例:オブジェクト配列からカテゴリごとの合計値を計算する

ある商品リストがあり、各商品にはcategoryとpriceプロパティがあります。
目的は、各カテゴリごとに商品価格の合計を計算することです。

const products = [
    { name: 'コーヒーメーカー', category: '家電', price: 3000 },
    { name: 'スニーカー', category: 'ファッション', price: 8000 },
    { name: 'ノートブック', category: '文房具', price: 500 },
    { name: 'タブレット', category: '家電', price: 40000 },
    { name: 'Tシャツ', category: 'ファッション', price: 1500 },
];

const totalByCategory = products.reduce((accumulator, product) => {
    if (!accumulator[product.category]) {
        accumulator[product.category] = 0;
    }
    // 現在の商品の価格を、そのカテゴリの合計値に加算します。
    accumulator[product.category] += product.price;
    return accumulator;
}, {});

console.log(totalByCategory);
//出力結果:{ '家電': 43000, 'ファッション': 9500, '文房具': 500 }

このコードでは、reduce関数を使用してproducts配列を処理し、カテゴリごとに価格の合計を計算しています。

初期値として空のオブジェクト{}を渡し、各商品を処理する際には、現在の商品のカテゴリが累積オブジェクトにまだ存在しない場合は、そのカテゴリをキーとして新たに追加し、価格を初期値として設定します。

カテゴリが既に存在する場合は、そのカテゴリの合計値に現在の商品の価格を加算します。最終的に、各カテゴリごとの商品価格の合計を含むオブジェクトが生成されます。

この例では、reduce関数を活用して配列内のオブジェクトをもとにデータ構造を構築し、データを効率的に集約するアプローチを紹介しています。

この手法は、データをカテゴリ別に分類し、それぞれの合計を算出するなど、実務で遭遇する様々な状況で役立ちます。

応用例:データのフィルタリングと変換

想定するシナリオとして、商品の配列から特定の条件を満たす商品のみを抽出し、
その抽出された商品の情報を変換して新たな配列を生成する場合を考えます。
このプロセスには、商品が特定の価格範囲にあるかどうかを判断し、
その後、抽出された商品の価格に税を適用して最終価格を計算します。

const products = [
    { name: 'コーヒーメーカー', category: '家電', price: 3000 },
    { name: 'スニーカー', category: 'ファッション', price: 8000 },
    { name: 'ノートブック', category: '文房具', price: 500 },
    { name: 'タブレット', category: '家電', price: 40000 },
    { name: 'Tシャツ', category: 'ファッション', price: 1500 },
];

// 特定の条件(ここでは価格が1000円以上の商品)を満たす商品を抽出し、
// 抽出された商品に対して税込み価格(10%を加算)を計算します。
const filteredAndTransformed = products.reduce((accumulator, product) => {
    if (product.price >= 1000) {
        const priceWithTax = product.price * 1.1; // 税込み価格を計算
        accumulator.push({ ...product, priceWithTax }); // 変換後の商品情報を追加
    }
    return accumulator;
}, []);

console.log(filteredAndTransformed);
/*
[
  {
    name: 'コーヒーメーカー',
    category: '家電',
    price: 3000,
    priceWithTax: 3300.0000000000005
  },
  {
    name: 'スニーカー',
    category: 'ファッション',
    price: 8000,
    priceWithTax: 8800
  },
  { name: 'タブレット', category: '家電', price: 40000, priceWithTax: 44000 },
  {
    name: 'Tシャツ',
    category: 'ファッション',
    price: 1500,
    priceWithTax: 1650.0000000000002
  }
*/

このコードでは、reduce関数を使って、条件に一致する商品をフィルタリングしつつ、
その商品情報を変換して新しい配列を作成しています。
この操作が一連の流れで完結しているため、データ処理が効率的に行われることがわかるかと思います。

パフォーマンス観点 

JavaScriptにおいて、データを扱う際には様々な配列操作メソッドが利用可能です。
map、filter、reduceはその中でも特によく使用されるメソッドです。
これらは似たような処理を行うことができるため、特定のシナリオでどれを選択すべきか迷うことがあります。
パフォーマンスの観点からこれらのメソッドを比較することで、最適な選択肢を理解することが重要です。

mapとfilterの基本

1.map: 配列の各要素に対して関数を実行し、結果を新しい配列に格納します。データの変換や加工に適しています。
2.filter: 条件に一致する要素のみを新しい配列に抽出します。データセットから特定の要素を選別する際に便利です。

パフォーマンス比較

mapやfilterを連続して使用する場合、配列を複数回走査することになります。
例えば、先にfilterで特定の要素を抽出し、その結果に対してmapで変換を行うと、2回の走査が必要です。
これに対して、reduceを使用すれば、フィルタリングと変換を一つの走査で実行できます。

// mapとfilterの連鎖使用例
const filteredAndMapped = products
    .filter(product => product.price >= 1000)
    .map(product => ({ ...product, priceWithTax: product.price * 1.1 }));

// reduceを使用した同等の操作
const reduced = products.reduce((acc, product) => {
    if (product.price >= 1000) {
        acc.push({ ...product, priceWithTax: product.price * 1.1 });
    }
    return acc;
}, []);

データ量が少ない状況では、mapfilterを使用する方がコードが直感的でわかりやすくなることが多いです。
これらのメソッドは、それぞれが特定の目的(変換やフィルタリング)に特化しているため、
コードの意図が明確に伝わりやすくなります。
しかし、複数の処理を一度に実行する必要がある場合、
reduceメソッドを選択することで、パフォーマンスの面で利点があります。
これは、reduceが一連の操作を単一の走査で処理できるため、データの再走査を避けることができるからです。
したがって、処理すべきデータの量が多い場合や、複数の操作を効率的に組み合わせたい場合には、reduceが特に有効です。

まとめ

この記事を通じて、reduce関数の基本概念から実践的な応用例までを見てきました。
最初は複雑に感じるかもしれないreduce関数ですが、その処理の流れと使い方を理解すれば、コードの実行効率を高め、プログラムのパフォーマンスを向上させることが可能です。
一つのループで複数の処理をまとめて行えるため、
データの走査回数を減らし、よりスマートなコードを書くことができます。

最後まで、見ていただきありがとうございました!!

6
3
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?