Reputation: 13409
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
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
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
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
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