Как я могу оптимально сгруппировать список объектов по их подобъекту? - PullRequest
0 голосов
/ 07 июня 2018

Я пытаюсь сгруппировать некоторые объекты 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'}]
]

Как бы то ни было, нам просто нужны люди, сгруппированные по комнатам по линейному времени.

Я пробовал Лодаша groupBy, используяи map, и reduce, просто выполняю циклы for, которые составляют список, и т. д. Я в замешательстве, потому что, не имея возможности использовать литерал объекта (комнату) в качестве хеш-индекса, я не знаюкак эффективно группировать внешние объекты по внутренним объектам.

Любая помощь очень полезнаPreciated.

Обновление: добавление ясности в попытке сделать это с линейной сложностью по времени - наиболее эффективный эквивалент этого кода Ruby:

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

Ответы [ 3 ]

0 голосов
/ 07 июня 2018

Одна альтернатива - использовать 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);
0 голосов
/ 07 июня 2018

Вы можете решить эту проблему, используя lodash#groupBy и lodash#map, чтобы собрать и преобразовать каждую группу.Кроме того, мы используем lodash#omit для удаления объекта room из каждого person из массива people.

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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
0 голосов
/ 07 июня 2018

Вы можете использовать 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).

...