JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

JavaScriptで配列のオブジェクトをyearプロパティごとにグループ化する方法

JavaScriptでデータを扱っていると、共通のプロパティ値を持つオブジェクト同士を1つのグループにまとめたいケースはよくあります。本記事では、年(year)週(week)の情報を持つオブジェクト配列を、yearプロパティの値ごとにグループ化して新しい配列を作る方法を、サンプルコードと実行結果付きで解説します。

元となるデータ

今回入力として想定するのは、次のようなオブジェクトが並んだ配列です。

const arr = [
    { year: 2017, week: 45 },
    { year: 2017, week: 46 },
    { year: 2017, week: 47 },
    { year: 2017, week: 48 },
    { year: 2017, week: 50 },
    { year: 2017, week: 52 },
    { year: 2018, week: 1 },
    { year: 2018, week: 2 },
    { year: 2018, week: 5 }
];

目指す出力

この配列を受け取り、yearプロパティの値が同じ要素どうしを1つのグループにまとめた新しい配列を返す関数を作成します。イメージとしては、次のように「年をキーとしたオブジェクトの中に、その年に属する週のリスト」が格納される構造です。

[
  { 2017: [45, 46, 47, 48, 50, 52] },
  { 2018: [1, 2, 5] }
]

なお、本記事の実装では各週の値を { value: 数値 } というオブジェクトの形で格納します。

実装例

実際のコードは以下の通りです。

const arr = [
    { year: 2017, week: 45 },
    { year: 2017, week: 46 },
    { year: 2017, week: 47 },
    { year: 2017, week: 48 },
    { year: 2017, week: 50 },
    { year: 2017, week: 52 },
    { year: 2018, week: 1 },
    { year: 2018, week: 2 },
    { year: 2018, week: 5 }
];

const groupByYear = arr => {
    const res = [];
    const map = {};

    arr.forEach(item => {
        const temp = {};
        // まだ登録されていない年なら、新しいグループを作成
        if (!map[item.year]) {
            map[item.year] = [];
            temp[item.year] = map[item.year];
            res.push(temp);
        }
        // 該当する年のグループに週データを追加
        map[item.year].push({ value: item.week });
    });

    return res;
};

console.log(JSON.stringify(groupByYear(arr), undefined, 4));

コードの解説

ポイントは、補助的なオブジェクト map を「ルックアップテーブル」として使う点です。処理の流れを整理すると次のようになります。

  • map: yearの値をキーとして、対応する週データの配列への参照を保持します。
  • res: 最終的に返却する結果配列です。「{ 2017: 配列 }」「{ 2018: 配列 }」といった形式のオブジェクトが格納されます。
  • forEach: 各要素を順に処理し、その年のエントリがまだ存在しない場合にだけ、新しい空配列を作成してresへ登録します。
  • 2回目以降に同じyearが現れたときは、既存の配列に対して { value: week } をpushするだけなので、重複したグループは作られません。

ここで重要なのが参照の仕組みです。temp[item.year] = map[item.year] によって、resに格納されるオブジェクトとmapが持つ配列は同じ実体を共有しています。そのため、map経由で追加したデータは自動的にres内の配列にも反映されます。

実行結果

コンソールには次のように出力されます。

[
    {
        "2017": [
            { "value": 45 },
            { "value": 46 },
            { "value": 47 },
            { "value": 48 },
            { "value": 50 },
            { "value": 52 }
        ]
    },
    {
        "2018": [
            { "value": 1 },
            { "value": 2 },
            { "value": 5 }
        ]
    }
]

2017年のデータ6件と2018年のデータ3件が、それぞれ年ごとに正しくグループ化されていることが確認できます。

reduceを使った別の書き方

より簡潔に関数型のスタイルで書きたい場合は、Array.prototype.reduce() を活用する方法もあります。

const groupByYear = arr =>
  Object.entries(
    arr.reduce((acc, { year, week }) => {
      (acc[year] ||= []).push({ value: week });
      return acc;
    }, {})
  ).map(([year, weeks]) => ({ [year]: weeks }));

まずreduceで「年をキーとした単一のオブジェクト」に全データを集約し、その後 Object.entries() でキーと値のペアに分解して、目的の配列形式へ変換しています。 ||= は論理OR代入演算子(ES2021)で、キーが未定義の場合にのみ新規配列を生成するため、if文による分岐が不要になります。

まとめ

配列のグループ化は、補助的なマップオブジェクトかreduceを組み合わせれば短いコードで実現できます。どちらの手法も計算量はO(n)で効率的であり、売上データの月別集計やグラフ描画用のデータ整形など、さまざまな場面で応用できます。ぜひ自分のプロジェクトでも活用してみてください。

  1. 【JavaScript】配列内のネストされたオブジェクトの値を合計する方法

    この記事では、JavaScriptを使って、配列内にネストされた(入れ子構造の)オブジェクトの値を合計する方法を解説します。サンプルコードでは、JSONデータの複数階層に格納された数値(costNum)を順番に取り出し、その合計をブラウザ上に表示します。 コード例 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=d

  2. 複数の値でJavaScript配列の要素を検索する方法

    JavaScriptである配列(arr)が、別の配列(arr1)の複数の値・すべての要素を含んでいるかどうかを確認したい場面はよくあります。そんなときは、every()メソッドとincludes()メソッドを組み合わせることで、シンプルに判定できます。以下はそのサンプルコードです。 コード例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content=