67472


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]

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

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


CC BY-SA 4.0
2020-06-09T11:03:55.157
67461
2020-07-02T07:08:07.773
2020-06-09T13:50:22.863
39448
39448
question
javascript
JavaScriptで、配列内のオブジェクトを任意のプロパティについて重複を排除し、オブジェクトにまとめたい


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

[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 の方法も十分良いと思います。


CC BY-SA 4.0
2020-06-09T21:45:17.543
67472
2020-06-09T21:45:17.543
32959
67461
answer


forEachで、十分にわかりやすいから、reduce 使うものよりもよいと思います。

push 後に filter で uniq を見るのはさすがに非効率なので  
indexOf で存在判定して、なければ、pushする、  
というのに変えた方がいいかと思います。

[code]

    let obj = {};
    data.forEach((e) => {
      if (Array.isArray(obj[e.target_id])) {
        if (obj[e.target_id].indexOf(e.name) !== -1) {
          obj[e.target_id].push(e.name);
        }
      } else {
        obj[e.target_id] = [e.name];
      }
    })
    
[/code]

obj[e.target_id]
のFalse属性判定は、「===」を使わずに「==」を使うのと同じで、不具合の温床になってしまうので避けた方がいいでしょう。  
name に数値の0や空文字が紛れ込んだら見つけにくいバグになる感じがします。  
上記コードでは念の為isArray判定しました。

Setもいいですが、速度的に100要素くらいまではindexOfの方が早いっぽいのでそのように書きました。  
下記リンク記事のコメント欄まで読んでもらえるとわかると思います。  
(uniq関数を作った方がいいとは思います)

JavaScriptで重複排除を自分で実装してはいけない（Setを使う） - Qiita  
<https://qiita.com/netebakari/items/7c1db0b0cea14a3d4419>

JavaScriptのArrayでuniqする8つの方法（と、その中で最速の方法） - Qiita  
<https://qiita.com/piroor/items/02885998c9f76f45bfa0>


CC BY-SA 4.0
2020-06-26T14:11:18.200
68037
2020-07-02T07:08:07.773
2020-07-02T07:08:07.773
21047
21047
67461
answer