🐡

[JavaScript]配列に格納されているオブジェクトをグルーピング

2022/07/04に公開2

何番煎じかと思いますが、実装の機会があったのでまとめておきます。

やりたいこと

配列に格納されているオブジェクトを

[
  { fruit_id: 1, option_name: 'バナナ' },
  { fruit_id: 1, option_name: 'バナナ' },
  { fruit_id: 2, option_name: 'りんご' },
  { fruit_id: 2, option_name: 'りんご' },
  { fruit_id: 2, option_name: 'りんご' },
  { fruit_id: 3, option_name: 'ぶどう' },
  { fruit_id: 3, option_name: 'ぶどう' }
]

fruit_id ごとに配列でまとめます。

[
  [
    { fruit_id: 1, option_name: 'バナナ' },
    { fruit_id: 1, option_name: 'バナナ' }
  ],
  [
    { fruit_id: 2, option_name: 'りんご' },
    { fruit_id: 2, option_name: 'りんご' },
    { fruit_id: 2, option_name: 'りんご' }
  ],
  [
    { fruit_id: 3, option_name: 'ぶどう' },
    { fruit_id: 3, option_name: 'ぶどう' }
  ]
]

方針

Mapを利用します。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

fruit_idkey にして valuefruit_id ごとの配列を格納していきます。

実装

const fruits = [
    {
        fruit_id: 1,
        option_name: "バナナ"
    },
    {
        fruit_id: 1,
        option_name: "バナナ"
    },
    {
        fruit_id: 2,
        option_name: "りんご"
    },
    {
        fruit_id: 2,
        option_name: "りんご"
    },
    {
        fruit_id: 2,
        option_name: "りんご"
    },
    {
        fruit_id: 3,
        option_name: "ぶどう"
    },
    {
        fruit_id: 3,
        option_name: "ぶどう"
    }
]

const formatedFruits = [];
const nMap = new Map();

for (let i = 0; i < fruits.length; i++) {
    const fruit = fruits[i]
    if (nMap.get(fruit.fruit_id)) {
        const tempArray = nMap.get(fruit.fruit_id);
        tempArray.push(fruit);
        nMap.set(fruit.fruit_id, tempArray);
    } else {
        nMap.set(fruit.fruit_id, [fruit]);
    }
}

for (const fruitsPerFruitID  of nMap.values()) {
    formatedFruits.push(fruitsPerFruitID)
}

解説

空の配列と Map インスタンスを生成します。

const formatedFruits = [];
const nMap = new Map();

for文でループして fruit を抽出します。
Mapget メソッドを使用して key に値が格納されているか確認します。

const fruit = fruits[i]
// key に値があるか確認
if (nMap.get(fruit.fruit_id)) {

値がある = 配列が格納されている想定のため、配列の値を取得します。
配列の push メソッドを使用して fruit を格納します。
Mapset メソッドで fruit_idkey にした新たな配列をセットします。

const tempArray = nMap.get(fruit.fruit_id);
tempArray.push(fruit);
nMap.set(fruit.fruit_id, tempArray);

それ以外の場合はまだ key に値がないということなので、配列としてセットします。

} else {
  nMap.set(fruit.fruit_id, [fruit]);
}

以上を繰り返すとこのような値になります。

console.log(nMap)
Map(3) {
  1 => [
    { fruit_id: 1, option_name: 'バナナ' },
    { fruit_id: 1, option_name: 'バナナ' }
  ],
  2 => [
    { fruit_id: 2, option_name: 'りんご' },
    { fruit_id: 2, option_name: 'りんご' },
    { fruit_id: 2, option_name: 'りんご' }
  ],
  3 => [
    { fruit_id: 3, option_name: 'ぶどう' },
    { fruit_id: 3, option_name: 'ぶどう' }
  ]
}

これを values メソッドを使用して Map 内の value を出力し、用意しておいた配列にガシガシ入れます。

for (const fruitsPerFruitID  of nMap.values()) {
    formatedFruits.push(fruitsPerFruitID)
}

以上です。
いろんなやり方あると思います。よかったら皆さんのやり方教えてください。

Discussion

nap5nap5

いろんなやり方あると思います。よかったら皆さんのやり方教えてください。

tidyjsでやってみました

定義側

import { tidy, distinct, groupBy } from "@tidyjs/tidy";

export const groupData = <T extends { id: number | string }>(data: T[], isUnique: boolean = false): T[] => {
  const result = tidy(data, groupBy(["id"], groupBy.entries({
    mapLeaves(values) {
      if (isUnique) return tidy(values, distinct(["id"]))
      return values
    },
  })))
  if (isUnique) return result.flatMap(([key, values]) => values) as T[]
  return result.map(([key, values]) => values) as T[]
}

使用側

import { test, expect, describe } from "vitest";
import { groupData } from ".";

type Fruit = {
  id: number;
  name: string;
};

const data: Fruit[] = [
  {
    id: 1,
    name: "バナナ",
  },
  {
    id: 1,
    name: "バナナ",
  },
  {
    id: 2,
    name: "りんご",
  },
  {
    id: 2,
    name: "りんご",
  },
  {
    id: 2,
    name: "りんご",
  },
  {
    id: 3,
    name: "ぶどう",
  },
  {
    id: 3,
    name: "ぶどう",
  },
];

describe("Groupnaized", () => {
  test("Enable uniq", () => {
    const results = groupData(data, true);
    expect(results).toStrictEqual([
      { id: 1, name: "バナナ" },
      { id: 2, name: "りんご" },
      { id: 3, name: "ぶどう" },
    ]);
  });
  test("Disable uniq", () => {
    const results = groupData(data, false);
    expect(results).toStrictEqual([
      [
        { id: 1, name: "バナナ" },
        { id: 1, name: "バナナ" },
      ],
      [
        { id: 2, name: "りんご" },
        { id: 2, name: "りんご" },
        { id: 2, name: "りんご" },
      ],
      [
        { id: 3, name: "ぶどう" },
        { id: 3, name: "ぶどう" },
      ],
    ]);
  });
});

demo code.

https://codesandbox.io/p/sandbox/kind-khorana-89jn86?file=%2Fsrc%2Findex.ts%3A1%2C1