V. Sambor
V. Sambor

Reputation: 13409

Javascript object reorganization with performance

I am working on a problem where I have to group an array of objects from one form into another.

An example is better than 1000 words:

var initialData = [
  {
    house: { id: 1, text: "white" },
    room: { id: 1, text: "red" },
    price: 2.1
  },
  {
    house: { id: 1, text: "white" },
    room: { id: 2, text: "blue" },
    price: 3.1
  },
  {
    house: { id: 1, text: "white" },
    room: { id: 3, text: "red" },
    price: 5.8
  },
  {
    house: { id: 2, text: "black" },
    room: { id: 1, text: "yellow" },
    price: 9.1
  },
  {
    house: { id: 2, text: "black" },
    room: { id: 2, text: "green" },
    price: 7.7
  },
];

And the new object should look like this:

var finalObject = {
  houses: [
    {
      id: 1, text: "white",
      rooms: [
        { id: 1, text: "red", price: "2.1" },
        { id: 2, text: "blue", price: "3.1" },
        { id: 3, text: "red", price: "5.8" }
      ]
    },
    {
      id: 2, text: "black",
      rooms: [
        { id: 1, text: "yellow", price: "9.1" },
        { id: 2, text: "green", price: "7.7" }
      ]
    }
  ]
};

I have to find unique houses with all their rooms and also to add each price from initial object inside room.

I'm wondering which would be the best way to do this since I will have a huge amount of elements?

I have some ideas with multiple loops, but to me it seems a bit too complex my solution.

Update: my question is not the same as the one candidate for duplication because I don't use lodash, and my object has to be refactored a bit, not just regrouped.

Possible solution (inspired by @Gael's answer)

finalObject = {}

for (var i = 0; i < initialData.length; ++i) {
  var item = initialData[i];
  var id = item.house.id;
  if(!finalObject[id]) {
    finalObject[id] = item.house;
    finalObject[id].rooms = [];
  }
  var room = item.room;
  room.price = item.price;

  finalObject[id].rooms.push(room);
}

console.log(finalObject);

Upvotes: 5

Views: 376

Answers (4)

kamoroso94
kamoroso94

Reputation: 1735

Depending on if you want to preserve the initial data or not will affect the solution. I've assumed you want to keep the original object and produce a new one.

You can do this in a single loop if you use a Map object to remember where existing houses are by there id property. If the house has already been inserted into the result, you just need to add the new room. Otherwise, you need to create a new house object and store it's index in the map.

function transformHouses(houses) {
  const houseMap = new Map();
  const result = {houses: []};

  for(const house of houses) {
    if(houseMap.has(house.id)) {
      const index = houseMap.get(house.id);
      const room = Object.assign({price: house.price}, house.room);

      result.houses[index].rooms.push(room);
    } else {
      const room = Object.assign({price: house.price}, house.room);
      const entry = Object.assign({rooms: [room]}, house.house)

      housesMap.set(house.id, result.houses.length);
      result.houses.push(entry);
    }
  }

  return result;
}

Upvotes: 1

Ori Drori
Ori Drori

Reputation: 192477

Use Array#reduce with a helper object:

var initialData = [{"house":{"id":1,"text":"white"},"room":{"id":1,"text":"red"},"price":2.1},{"house":{"id":1,"text":"white"},"room":{"id":2,"text":"blue"},"price":3.1},{"house":{"id":1,"text":"white"},"room":{"id":3,"text":"red"},"price":5.8},{"house":{"id":2,"text":"black"},"room":{"id":1,"text":"yellow"},"price":9.1},{"house":{"id":2,"text":"black"},"room":{"id":2,"text":"green"},"price":7.7}];

var dict = {}; // helper object
var result = initialData.reduce(function(houses, obj) { // reduce the data
  var house = dict[obj.house.id]; // get the house from the dict by id
  
  if(!house) { // if house wasn't found
    house = Object.assign({}, obj.house, { rooms: [] }); // create a new house object
    houses.push(house); // push it into the array of houses
    dict[house.id] = house; // add it to the dict by id
  }
  
  house.rooms.push(obj.room); // push the room to the current house
  
  return houses;
}, []);

console.log(result);

You can also achieve it using ES6 Map and spread syntax:

const initialData = [{"house":{"id":1,"text":"white"},"room":{"id":1,"text":"red"},"price":2.1},{"house":{"id":1,"text":"white"},"room":{"id":2,"text":"blue"},"price":3.1},{"house":{"id":1,"text":"white"},"room":{"id":3,"text":"red"},"price":5.8},{"house":{"id":2,"text":"black"},"room":{"id":1,"text":"yellow"},"price":9.1},{"house":{"id":2,"text":"black"},"room":{"id":2,"text":"green"},"price":7.7}];

const result = [...initialData.reduce((houses, { house, room }) => { // reduce the data to a Map
  const currentHouse = houses.get(house.id) || Object.assign({}, house, { rooms: [] }); // get the current house from the map by id, or create a new one
  
  currentHouse.rooms.push(room); // push the room to the current house
  
  return houses.set(currentHouse.id, currentHouse); // set the house to the map, and return it
}, new Map()).values()]; // get the values of the map and spread to an array

console.log(result);

Upvotes: 4

Ga&#235;l Barbin
Ga&#235;l Barbin

Reputation: 3919

var houses= {};

initialData.forEach(function(item){
  
  if( !houses[ item.house ] ){
  
    houses[ item.house ]= item.house;
    houses[ item.house ].rooms= {};
  }
  houses[ item.house ].rooms[ item.room.id ]= item.room;
  houses[ item.house ].rooms[ item.room.id ].price= item.price;
  
});

Upvotes: 1

Pritam Banerjee
Pritam Banerjee

Reputation: 18958

You should use a map:

var myMap = new Map();

var keyObj = {}, // ideally your room object with id


// setting the values
myMap.set(keyString, "value associated with 'a string'"); // not recommended for your case

Or:

myMap.set(keyObj, 'value associated with keyObj'); // should be rooms

myMap.size; // the number of items you added

Upvotes: 2

Related Questions