如何通过子对象对对象列表进行最佳分组?

时间:2018-06-06 23:52:44

标签: javascript grouping lodash graphql

我试图通过共享的类似对象对一些JavasScript对象进行分组。我可以在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'
    }
  }
]

在用户界面中,我们会按照他们所在的房间显示对象。我们需要保留对房间本身的引用,否则我们只需按房间属性排序

我尝试做的是将数据重塑为以下内容:

{
  {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'}]
]

然而,它出来了,我们只需要在线性时间内按房间分组的人。

我已使用groupBymap尝试了lodash的reduce,只是执行将列表放在一起的for循环等。我不知道因为不能使用对象文字(房间)作为哈希索引,我不知道如何通过内部对象有效地对外部对象进行分组。

非常感谢任何帮助。

更新:增加了关于尝试使用线性时间复杂度的清晰度 - 这个Ruby代码最有效的等价物:

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

3 个答案:

答案 0 :(得分:1)

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

people

&#13;
&#13;
var result = _(data)
  .groupBy('room.id')
  .map(people => ({
    room: { ...people[0].room }, 
    people: _.map(people, person => _.omit(person, 'room'))
  })).value();
&#13;
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);
&#13;
&#13;
&#13;

答案 1 :(得分:0)

您可以使用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对象只能有字符串(/ number)属性。

答案 2 :(得分:0)

另一种方法是使用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);