Converting array of object to hierarchical data structure - javascript

I have an original array and I want to plot it in Sunburst map which needs a hierarchical data structure.
[
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2},
]
Desired Result
[
{
id: Asia,
children:[{
id: India,
children:[{
id: Delhi,
value: 41,
}]
},
{
id:China,
children:[{
id: Beijing
value: 2,
}]
}]
},
{
id: Europe,
children: [{
id: Germany,
children: [{
id: Berlin,
value: 24,
}]
},
{
id: England,
children: [{
id: London,
value: 3,
}]
}]
},
{
id: NorthAmerica,
children:[{
id: USA,
children:[{
id: NewYork,
value: 4,
},
{
id: Boston,
value: 3,
},
{
id: Chicago,
value: 3,
}]
}]
},
{
id: Austrailia
children: [{
id:Sydney,
value: 4,
}]
},
]
can anyone help me with this, I tried using reduce method but I am not able to get the desired result.
PS : It would be super useful if anyone could suggest an answer that would deal with n number of ids separated by commas. For ex: here we have 3 id hierarchy separated by commas, what would happen if there were 4 or 5 depth data.

A simple solution with recursion:
const data = [
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,Chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2},
];
const addChild = (ids, value, arr) => {
const id = ids.shift();
let index = arr.findIndex(item => item.id === id);
if (index < 0) {
arr.push({id, children: []});
index = arr.length - 1;
}
if (ids.length > 0) {
const children = arr[index].children;
addChild(ids, value, children);
}
else
arr[index].value = value;
}
const treeData = data.reduce((tree, item) => {
const ids = item.id.split(',');
addChild(ids, item.value, tree);
return tree;
}, []);
console.log(treeData);

To build a hierarchy of objects from your input is fairly straightforward, you dont even need to do anything recursive a loop + reduce will do it. This will work with any number of levels in your comma separated list.
const input = [
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2}
]
const result = input.map(o => ({ids:o.id.split(","), value:o.value})).reduce( (acc,obj) => {
let curr = acc;
let id;
while( (id = obj.ids.shift()) != null ){
if(!curr[id])
curr[id] = {};
curr = curr[id];
}
curr.value = obj.value
return acc;
},{});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
To then turn this into the format you wanted does take a bit of recursion:
const input = [
{id: "Asia,India,NewDelhi", value: 41},
{id: "Europe,Germany,Berlin", value: 24},
{id: "Europe,England,London", value: 3},
{id: "NorthAmerica,USA,NewYork", value: 4},
{id: "NorthAmerica,USA,Boston", value: 3},
{id: "NorthAmerica,USA,chicago", value: 3},
{id: "Austrailia,Sydney", value: 4},
{id: "Asia,China,Beijing", value: 2}
]
const result = input.map(o => ({ids:o.id.split(","), value:o.value})).reduce( (acc,obj) => {
let curr = acc;
let id;
while( (id = obj.ids.shift()) != null ){
if(!curr[id])
curr[id] = {};
curr = curr[id];
}
curr.value = obj.value
return acc;
},{});
function buildHierarchy(input){
return Object.entries(input).map( ([id,children]) => {
if(children.value){
return {id,value:children.value}
}
return {id, children: buildHierarchy(children)}
})
}
console.log(buildHierarchy(result));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

delete an object in an array based on index at level 1 and level 2

I have a array of object which has sub arrays, I want to delete the subarray object based on the index from both the levels.
For example if want to delete object with id:12 I should pass firstIndex = 0 and secondIndex = 1;
Pls note above mentioned eg: is for better understanding of question and not to delete element by id should be deleted based on index only(use case is different and index are received from different source).
const array = [
{
id: 1,
row: [
{id: 11, value: x},
{id: 12, value: y},
{id: 13, value: z},
],
},
{
id: 2,
row: [
{id: 21, value: a},
{id: 22, value: b},
{id: 23, value: c},
],
}
]
firstIndex = 1, secondIndex = 2
const result = [
{
id: 1,
row: [
{id: 11, value: x},
{id: 12, value: y},
{id: 13, value: z},
],
},
{
id: 2,
row: [
{id: 21, value: a},
{id: 22, value: b},
],
}
]
firstIndex = 0, secondIndex = 1
const result = [
{
id: 1,
row: [
{id: 11, value: x},
{id: 13, value: z},
],
},
{
id: 2,
row: [
{id: 21, value: a},
{id: 22, value: b},
{id: 23, value: c},
],
}
]
const result = array.map((el, i) => (i === firstIndex) && el.rows.map(elm, (elm, index) => (index === secondIndex ) && elm.splice(index, 1)))
You can remove Item from array by splice method like this:
const array = [
{
id: 1,
row: [
{id: 11, value: 'x'},
{id: 12, value: 'y'},
{id: 13, value: 'z'},
],
},
{
id: 2,
row: [
{id: 21, value: 'a'},
{id: 22, value: 'b'},
{id: 23, value: 'c'},
],
}
]
firstIndex = 1, secondIndex = 2;
array[firstIndex].row.splice(secondIndex, 1);
console.log(array)
By adding the optional chaining: ? you assure that the code works even if your indexes are wrong, it then just doesn't change anything and you won't get an error
const array = [
{ id: 1, row: [{ id: 11, value: "x" }, { id: 12, value: "y" }, { id: 13, value: "z" }, ], },
{ id: 2, row: [{ id: 21, value: "a" }, { id: 22, value: "b" }, { id: 23, value: "c" }, ], }
]
function deleteByIndex(array, index1, index2) {
array[index1]?.row?.splice(index2, 1)
return array
}
console.log(deleteByIndex(array, 1, 2))

How to use find() method on nested arrays?

In this example you can see that find method works fine in this array:
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5}
];
console.log(inventory.find(fruit => fruit.name === 'cherries'));
// { name: 'cherries', quantity: 5 }
Once I add one more level and trying to find item in it it just dont find it, it shows undefined:
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
console.log(inventory.find(fruit => fruit.name === 'bing'));
// undefined
// should be: { name: 'bing', quantity: 2 }
So I guess there is some other way to do this, but i dont know it and cant find nothing.
Your code isn't allowing for the optional type array. Assuming you want to do a depth-first search, you'd make your callback a named function and use it recursively, see comments:
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
// Define the function
function find(array, name) {
// Loop the entries at this level
for (const entry of array) {
// If we found it, return it
if (entry.name === name) {
return entry;
}
// If not but there's a type array, recursively search it
if (Array.isArray(entry.type)) {
const found = find(entry.type, name);
if (found) {
// Recursive search found it, return it
return found;
}
}
}
// If execution falls off the end, it's effectively `return undefined;`
}
console.log(find(inventory, 'bing'));
inventory.find will only find element of array with given conditions in the inventory and because {name: 'bing', quantity: 2} is not present in inventory so it will return undefined
.You can do that using recursion
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
function findFruitWithName(arr,name){
let x = arr.find(fruit => fruit.name === name);
if(x === undefined){
for(let fruit of arr){
if(fruit.type) {
let y = findFruitWithName(fruit.type,name);
if(y !== undefined) return y
}
}
}
else return x;
}
console.log(findFruitWithName(inventory,'bing'))
// undefined
// should be: { name: 'bing', quantity: 2 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
You are right in that find only looks at the elements in the array and doesn't look in the nested ones, so you have to create your own.
You could loop over all the items in the inventory and collect all the elements in the potential type arrays and continue to loop over them until you find an item with the desired name, or return undefined if you check all items and you don't find a match.
Example
const inventory = [
{ name: "apples", quantity: 2 },
{ name: "bananas", quantity: 0 },
{
name: "cherries",
quantity: 5,
type: [{ name: "rainier", quantity: 3 }, { name: "bing", quantity: 2 }]
}
];
function findItem(inventory, name) {
let items = [...inventory];
let item;
while (items.length !== 0) {
item = items.pop();
if (item.name === name) {
return item;
} else if (item.type) {
items.push(...item.type);
}
}
return undefined;
}
console.log(findItem(inventory, "bing"));
try this
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
var name = 'bing'
var result = inventory.find((fruit) => {
if(fruit.name === name) return true
if(fruit.type) return fruit.type.find(type=>type.name===name)
return false
})
console.log(result)
UPDATE:
if you want it to make it recursive you can try
function find(name, inventory) {
return inventory.find((fruit) => {
if(fruit.name === name) return true
if(fruit.type) return find(name, fruit.type)
return false
})
}
console.log(find(name,inventory))

How to change key for array's objects' in javascript?

var originalArray = [
{Name: "IPHONE8s", Id: 4},
{Name: "Iphone 9", Id: 5},
{Name: "IPHONEX", Id: 6}
];
how to change it to be something like this
var changeArray = [
{text: "IPHONE8s", value: 4 },
{text: "Iphone 9", value: 5 },
{text: "IPHONEX", value: 6 }
]
Thank you very much.
Use Array.map()
var originalArray = [
{Name: "IPHONE8s", Id: 4},
{Name: "Iphone 9", Id: 5},
{Name: "IPHONEX", Id: 6}
];
var changeArray = originalArray.map(data => ({text: data.Name, value: data.Id}))
console.log(changeArray);
You can use .map() with Object destructuring:
let data = [
{Name: "IPHONE8s", Id: 4},
{Name: "Iphone 9", Id: 5},
{Name: "IPHONEX", Id: 6}
];
let result = data.map(({Name:text, Id:value}) => ({text, value}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
References:
Array.prototype.map()
Object destructuring
Example code
function renameKeys(obj, newKeys) {
const keyValues = Object.keys(obj).map(key => {
const newKey = newKeys[key] || key;
return { [newKey]: obj[key] };
});
return Object.assign({}, ...keyValues);
}
And use it like:
const obj = { a: "1", b: "2" };
const newKeys = { a: "A", c: "C" };
const renamedObj = renameKeys(obj, newKeys);
console.log(renamedObj)
You could map the objects by mapping the key value pairs with a new name by using an object for the keys and their new name for replacement.
This proposal preserves other properties.
var array = [{ Name: "IPHONE8s", Id: 4, foo: 'bar' }, { Name: "Iphone 9", Id: 5 }, { Name: "IPHONEX", Id: 6 }],
replace = { Name: 'text', Id: 'value' },
result = array.map(o => Object.assign(...Object
.entries(o)
.map(([k, v]) => ({ [replace[k] || k]: v }))
));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var originalArray = [{Name: "IPHONE8s", Id: 4}, {Name: "Iphone 9", Id: 5}, {Name:
"IPHONEX", Id: 6} ];
let changeArray = []
for (let i = 0; i < originalArray.length; i++) {
let tmpObj = {}
tmpObj.text = originalArray[i].Name
tmpObj.value = '' + originalArray[i].Id
changeArray.push(tmpObj)
}
console.log(changeArray)

How to sum the same objects in array

It is possible to sum the values of an array if they are the same like this:
var COLLECTION = [
{
"coords":[1335,2525],
"items":[
{id: "boletus",qty: 1},
{id: "lepiota",qty: 3},
{id: "boletus",qty: 2},
{id: "lepiota",qty: 4},
{id: "carbonite",qty: 4},
],
},
{
"coords":[1532,2889],
"items":[
{id: "boletus",qty: 2},
{id: "lepiota",qty: 6},
{id: "boletus",qty: 1},
{id: "lepiota",qty: 4},
{id: "chamomile",qty: 4},
],
}]
To return something like this:
var COLLECTION = [
{
"coords":[1335,2525],
"items":[
{id: "boletus",qty: 3},
{id: "lepiota",qty: 7},
{id: "carbonite",qty: 4},
],
},
{
"coords":[1532,2889],
"items":[
{id: "boletus",qty: 3},
{id: "lepiota",qty: 10},
{id: "chamomile",qty: 4},
],
}]
Wihout losing the other parts of the array?
(doing by hand is hard because I have more than 10 thousand duplicates like the example above, and the array have 600 thousand entries.
You could use map() to create new array and inside reduce() to group items objects by id and sum qty.
var data = [{"coords":[1335,2525],"items":[{"id":"boletus","qty":1},{"id":"lepiota","qty":3},{"id":"boletus","qty":2},{"id":"lepiota","qty":4},{"id":"carbonite","qty":4}]},{"coords":[1532,2889],"items":[{"id":"boletus","qty":2},{"id":"lepiota","qty":6},{"id":"boletus","qty":1},{"id":"lepiota","qty":4},{"id":"chamomile","qty":4}]}]
const result = data.map(function({coords, items}) {
return {coords, items: Object.values(items.reduce(function(r, e) {
if(!r[e.id]) r[e.id] = Object.assign({}, e)
else r[e.id].qty += e.qty
return r;
}, {}))}
})
console.log(result)
You could take the power of Map and render the result by using Array.from with a mapping function which builds new objects for items.
var COLLECTION = [{ coords: [1335, 2525], items: [{ id: "boletus", qty: 1 }, { id: "lepiota", qty: 3 }, { id: "boletus", qty: 2 }, { id: "lepiota", qty: 4 }, { id: "carbonite", qty: 4 }], }, { coords: [1532, 2889], items: [{ id: "boletus", qty: 2 }, { id: "lepiota", qty: 6 }, { id: "boletus", qty: 1 }, { id: "lepiota", qty: 4 }, { id: "chamomile", qty: 4 }] }];
COLLECTION.forEach(o => {
var map = new Map;
o.items.forEach(({ id, qty }) => map.set(id, (map.get(id) || 0) + qty));
o.items = Array.from(map, ([id, qty]) => ({ id, qty }));
});
console.log(COLLECTION);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use the functions forEach and reduce
This approach mutates the original array
var COLLECTION = [ { "coords":[1335,2525], "items":[ {id: "boletus",qty: 1}, {id: "lepiota",qty: 3}, {id: "boletus",qty: 2}, {id: "lepiota",qty: 4}, {id: "carbonite",qty: 4}, ], }, { "coords":[1532,2889], "items":[ {id: "boletus",qty: 2}, {id: "lepiota",qty: 6}, {id: "boletus",qty: 1}, {id: "lepiota",qty: 4}, {id: "chamomile",qty: 4}, ], }];
COLLECTION.forEach((o) => {
o.items = Object.values(o.items.reduce((a, c) => {
(a[c.id] || (a[c.id] = {id: c.id, qty: 0})).qty += c.qty;
return a;
}, {}));
});
console.log(COLLECTION);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you want to create a new array and keep the original data:
This approach uses the function map to create a new "cloned" array.
var COLLECTION = [ { "coords":[1335,2525], "items":[ {id: "boletus",qty: 1}, {id: "lepiota",qty: 3}, {id: "boletus",qty: 2}, {id: "lepiota",qty: 4}, {id: "carbonite",qty: 4}, ], }, { "coords":[1532,2889], "items":[ {id: "boletus",qty: 2}, {id: "lepiota",qty: 6}, {id: "boletus",qty: 1}, {id: "lepiota",qty: 4}, {id: "chamomile",qty: 4}, ] }],
result = COLLECTION.map(o => o);
result.forEach((o) => {
o.items = Object.values(o.items.reduce((a, c) => {
(a[c.id] || (a[c.id] = {id: c.id, qty: 0})).qty += c.qty;
return a;
}, {}));
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Add and combine duplicates in array of objects with lodash with specific conditions

Say I have a sorted array of objects like this:
[
{name: 'item1', quantity: 5},
{name: 'item1', quantity: 8},
{name: 'item2', quantity: 6},
{name: 'item2', quantity: 3},
{name: 'item3', quantity: 1},
{name: 'item3', quantity: 1},
]
I want to add up the values of items which have quantities > 1 and combine them so I get this:
[
{name: 'item1', quantity: 13},
{name: 'item2', quantity: 9},
{name: 'item3', quantity: 1},
{name: 'item3', quantity: 1},
]
Is there a quick single or chain of lodash methods that can achieve this? I was thinking to use _.map but it doesn't give you previous item, so I'd have to use a variable outside the _.map scope to keep that value. Seeing if I can do this with lodash since I'm already using it for other methods and not write extra lines of code.
If the extra condition is not possible, then combining and adding all items will have to do.
This is my attempt using only lodash:
var source = [
{name: 'item1', quantity: 5},
{name: 'item1', quantity: 8},
{name: 'item2', quantity: 6},
{name: 'item2', quantity: 3},
{name: 'item3', quantity: 1},
{name: 'item3', quantity: 1},
];
var predicate = function (e) {
return e.quantity > 1;
};
var result = _.chain(source)
.filter(predicate)
.groupBy(function (e) {
return e.name;
})
.map(function (group) {
return _.reduce(group, function (current, next) {
return {
name: next.name,
quantity: current.quantity + next.quantity
};
});
})
.union(_.filter(source, function (e) {
return !predicate(e);
}))
.value();
document.getElementById("output").textContent = JSON.stringify(result, 0, 2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js"></script>
<pre id="output"></pre>
Not sure if this is the most efficient way, as I'm not deeply familiar with lodash. Basically, the idea is this:
Get the elements with quantity > 1
Group by name
Produce sums for each name
Union with the elements with quantity <= 1
Fiddle
Here is a pure lodash-ian solution :)
It uses chain, reduce, toPairs, map and 1 temporary variable to do the job.
items = [
{name: 'item1', quantity: 5},
{name: 'item1', quantity: 8},
{name: 'item2', quantity: 6},
{name: 'item2', quantity: 3},
{name: 'item3', quantity: 1},
{name: 'item3', quantity: 1}
];
summary = _.chain(items).reduce(function(acc, i) {
if (i.quantity > 0) {
acc[i.name] = (acc[i.name] || 0) + i.quantity;
}
return acc;
}, {}).toPairs().map(function(x) {
var tmp = {};
tmp[x[0]] = x[1];
return tmp;
}).value();
console.log(JSON.stringify(summary));
// Outputs: [{"item1":13},{"item2":9},{"item3":2}]
Another solution in plain Javascript with a single loop.
var data = [{ name: 'item1', quantity: 5 }, { name: 'item1', quantity: 8 }, { name: 'item2', quantity: 6 }, { name: 'item2', quantity: 3 }, { name: 'item3', quantity: 1 }, { name: 'item3', quantity: 1 }],
combined = function (array) {
var r = [];
array.forEach(function (a, i) {
if (a.quantity === 1) {
r.push(a);
return;
}
if (!this[a.name]) {
this[a.name] = { name: a.name, quantity: 0 };
r.push(this[a.name]);
}
this[a.name].quantity += a.quantity;
}, {});
return r;
}(data);
document.write('<pre>' + JSON.stringify(combined, 0, 4) + '</pre>');
You can use Array.prototype.forEach() , for loop
var arr = [
{name: 'item1', quantity: 5},
{name: 'item1', quantity: 8},
{name: 'item2', quantity: 6},
{name: 'item2', quantity: 3},
{name: 'item3', quantity: 1},
{name: 'item3', quantity: 1},
];
var res = [];
arr.forEach(function(item, index) {
if (res.length === 0
|| !res.some(function(elem) {return elem.name === item.name})
|| item["quantity"] === 1 ) {
res.push(item)
} else {
for (var i = 0; i < res.length; i++) {
if (res[i]["name"] === item["name"]
&& (res[i]["quantity"] !== 1 && item["quantity"] !== 1)) {
res[i]["quantity"] += item["quantity"]
}
}
}
});
document.querySelector("pre").textContent = JSON.stringify(res, null, 2)
<pre></pre>

Categories