【问题标题】:How can I optimally group a list of objects by their sub object?如何按子对象对对象列表进行最佳分组?
【发布时间】:2018-06-06 23:52:43
【问题描述】:

我正在尝试按共享的相似对象对一些 JavaScript 对象进行分组。我可以在 Ruby 中毫不费力地做到这一点,但是对于我的一生(有点尴尬)无法在 JS 中以线性时间解决这个问题。 JS 似乎不允许对象字面量作为键,至少出于减少的目的。

作为 GraphQL 查询的结果,我有这样的数据:

[
  {
    id: 1,
    name: 'Bob',
    room: {
      id: 5,
      name: 'Kitchen'
    }
  },
  {
    id: 3,
    name: 'Sheila',
    room: {
      id: 5,
      name: 'Kitchen'
    }
  },
  {
    id: 2,
    name: 'Tom',
    room: {
      id: 3,
      name: 'Bathroom'
    }
  }
]

在 UI 中,我们将按对象所在的房间显示对象。我们需要保留对房间本身的引用,否则我们只需按房间属性排序。

我想要做的是将数据重塑成这样的:

{
  {id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
  {id: 3, name: 'Bathroom'}: [{id: 2, name: 'Tom'}]
}

如您所见,人们按他们所在的房间分组。

它也可以是这样的形状......

[ 
  { room: {id: 5, name: 'Kitchen'}, people: [{id: 1, name: 'Bob', ...}] }, 
  { room: {id: 3, name: 'Bathroom', people: [{id: 2, name: 'Tom'}]
]

不管结果如何,我们只需要按房间按线性时间分组的人。

我试过 lodash 的 groupBy,同时使用 mapreduce,只是做 for 循环将列表放在一起等等。我很难过,因为无法使用对象文字(房间)作为哈希索引,我不知道如何通过内部对象有效地对外部对象进行分组。

非常感谢任何帮助。

更新:增加关于尝试以线性时间复杂度执行此操作的清晰度 - 此 Ruby 代码的最有效等效项:

h = Hash.new { |h, k| h[k] = [] }
value.each_with_object(h) { |v, m| m[v[:room]] << v }

【问题讨论】:

    标签: javascript grouping lodash graphql


    【解决方案1】:

    您可以使用lodash#groupBylodash#map 来收集和转换每个组来解决此问题。此外,我们使用lodash#omitpeople 数组中的每个person 中删除room 对象。

    var result = _(data)
      .groupBy('room.id')
      .map(people => ({
        room: { ...people[0].room }, 
        people: _.map(people, person => _.omit(person, 'room'))
      })).value();
    

    var data = [
      {
        id: 1,
        name: 'Bob',
        room: {
          id: 5,
          name: 'Kitchen'
        }
      },
      {
        id: 3,
        name: 'Sheila',
        room: {
          id: 5,
          name: 'Kitchen'
        }
      },
      {
        id: 2,
        name: 'Tom',
        room: {
          id: 3,
          name: 'Bathroom'
        }
      }
    ];
    
    var result = _(data)
      .groupBy('room.id')
      .map(people => ({
        // make sure to create a new room object reference
        // to avoid mutability
        room: { ...people[0].room }, 
        people: _.map(people, person => _.omit(person, 'room'))
      })).value();
      
    console.log(result);
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 啊,好东西!我喜欢在map 之前巧妙地使用groupBy,然后使用数组的第一个元素来访问房间。我知道必须有某种方法可以通过 lodash 构建组合器。这是我登陆的内容:_(data.stuff).groupBy('room.id').map(people =&gt; ({room: people[0].room, people})).value()(房间复制传播对我们来说不是必需的,因为值来自 Apollo 并且已经是不可变的。我们也不想删除房间,只是按它们分组。)
    【解决方案2】:

    您可以使用reduce 创建一个按房间索引的人员对象,然后获取该对象的values,不需要库:

    const input=[{id:1,name:'Bob',room:{id:5,name:'Kitchen'}},{id:3,name:'Sheila',room:{id:5,name:'Kitchen'}},{id:2,name:'Tom',room:{id:3,name:'Bathroom'}}]
    
    const output = Object.values(
      input.reduce((a, { id, name, room }) => {
        const roomName = room.name;
        if (!a[roomName]) a[roomName] = { room, people: [] };
        a[roomName].people.push({ id, name });
        return a;
      }, {})
    );
    console.log(output);

    类似的对象

    {id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
    

    除非结构是Map,否则您的问题不能是这样的属性。普通的 Javascript 对象只能有字符串(/数字)属性。

    【讨论】:

      【解决方案3】:

      另一种方法是使用reduce 来按房间分组。

      const input = [{
          id: 1,
          name: 'Bob',
          room: {
            id: 5,
            name: 'Kitchen'
          }
        },
        {
          id: 3,
          name: 'Sheila',
          room: {
            id: 5,
            name: 'Kitchen'
          }
        },
        {
          id: 2,
          name: 'Tom',
          room: {
            id: 3,
            name: 'Bathroom'
          }
        }
      ];
      
      const res = input
        .map(person => ({
          person: {
            id: person.id,
            name: person.name
          },
          room: person.room
        }))
        .reduce((rooms, person) => {
          const room = rooms.find(room => room.id === person.room.id) || 
            { room: person.room };
          const idx = rooms.indexOf(room);
      
          room.people = room.people ?
            [...room.people, person.person] :
            [person.person];
      
          return Object.assign(rooms, {
            [idx === -1 ? rooms.length : idx]: room
          });
        }, []);
      
      console.log(res);

      【讨论】:

        猜你喜欢
        • 2018-05-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-21
        • 2020-04-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多