JavaScriptで、配列内のオブジェクトを任意のプロパティについて重複を排除し、オブジェクトにまとめたい


JavaScriptにおいて、配列内のオブジェクトを任意のプロパティについて重複を排除し新たにオブジェクトにまとめたいです。

例えば、以下のようなオブジェクトを持つ配列があった場合、

[code]

    const data = [
      {
        name: 'a',
        target_id: 2001
      },
      {
        name: 'b',
        target_id: 2001
      },
      {
        name: 'a',
        target_id: 2001
      },
      {
        name: 'd',
        target_id: 2002
      },
      {
        name: 'e',
        target_id: 2002
      },
      {
        name: 'f',
        target_id: 2002
      },
      {
        name: 'd',
        target_id: 2002
      },
    ]
    
[/code]

target_idが同一のものでまとめて、以下のようにnameの重複を排した形でオブジェクトにまとめたいです。

[code]

    { '2001': [ 'a', 'b' ], '2002': [ 'd', 'e', 'f' ] }
    
[/code]

私は下のようなコードを書いたのですが、より良い方法で実装する方法はありますでしょうか？

[code]

    let obj = {};
    data.forEach((e) => {
      if (obj[e.target_id]) {
        obj[e.target_id].push(e.name);
        obj[e.target_id] = obj[e.target_id].filter((x, i, self) => self.indexOf(x) === i);
    
      } else {
        obj[e.target_id] = [e.name];
      }
    })
    
[/code]

できればアウトプット用のオブジェクトに代入するようなやり方ではなく関数型で実現したいのですが..

ご教授いただけると幸いです。




代入を使わない記述法の例です。

[code]

    const x1 = data.reduce((acc, { name, target_id }) => ({
      ...acc,
      [target_id]: [... new Set([...(acc[target_id] || []), name])],
    }), {});
    
[/code]

`=` を使ってもいいなら次のようにも書けます。

[code]

    const x2 = data.reduce((acc, { name, target_id }) => {
      if (acc[target_id]) {
        acc[target_id] = [...new Set([...acc[target_id], name])];
      } else {
        acc[target_id] = [name];
      }
      return acc;
    }, {});
    
[/code]

重複を排除したいのであれば、`Array` の代わりに `Set` の使用を検討されてはいかがでしょうか？  
また集約した結果は `Object` より `Map` のほうが目的に適しているように感じました。

[code]

    const x3 = data.reduce(
      (acc, { name, target_id }) => acc.set(target_id, (acc.get(target_id) || new Set()).add(name)),
      new Map(),
    );
    
    // または
    
    const x4 = data.reduce((acc, { name, target_id }) => {
      if (acc.has(target_id)) {
        acc.get(target_id).add(name);
      } else {
        acc.set(target_id, new Set([name]));
      }
      return acc;
    }, new Map());
    
    // これらの場合 x3 や x4 のキーは String ("2001") ではなく Number (2001) になります。
    
[/code]

なおすべての例でパフォーマンスについては考慮していません。

個人的には質問文にある forEach の方法も十分良いと思います。




代入を使わない記述法の例です。

[code]

    const x1 = data.reduce((acc, { name, target_id }) => ({
      ...acc,
      [target_id]: [... new Set([...(acc[target_id] || []), name])],
    }), {});
    
[/code]

`=` を使ってもいいなら次のようにも書けます。

[code]

    const x2 = data.reduce((acc, { name, target_id }) => {
      if (acc[target_id]) {
        acc[target_id] = [...new Set([...acc[target_id], name])];
      } else {
        acc[target_id] = [name];
      }
      return acc;
    }, {});
    
[/code]

重複を排除したいのであれば、`Array` の代わりに `Set` の使用を検討されてはいかがでしょうか？  
また集約した結果は `Object` より `Map` のほうが目的に適しているように感じました。

[code]

    const x3 = data.reduce(
      (acc, { name, target_id }) => acc.set(target_id, (acc.get(target_id) || new Set()).add(name)),
      new Map(),
    );
    
    // または
    
    const x4 = data.reduce((acc, { name, target_id }) => {
      if (acc.has(target_id)) {
        acc.get(target_id).add(name);
      } else {
        acc.set(target_id, new Set([name]));
      }
      return acc;
    }, new Map());
    
    // これらの場合 x3 や x4 のキーは String ("2001") ではなく Number (2001) になります。
    
[/code]

なおすべての例でパフォーマンスについては考慮していません。

個人的には質問文にある forEach の方法も十分良いと思います。

