Change structure of an array - javascript

I have an array of objects with unknown depth. By depth i mean that i don't know does every child have children. I'm sorry i can't find proper words to explain it closer. This is example:
let array = [
{id: 1},
{id: 2, parentId: 1},
{id: 3, parentId: 2},
{id: 4, parentId: 3},
{id: 5, parentId: 2},
{id: 6, parentId: 1},
...
{id: 100, parentId: 90}
]
Is it possible to write function in javascript that will nest objects in order matching parent id?
[{id: 1, children: [{id: 2, ,children: [ {id: 3}]}] ,{ id: 6} ]} ] ...
What is the best practice to nest object to use it later to render data and not to ask in every layer if(children)?

Here is a method that creates a map to track which position in the array each item is. Then, the array is iterated through and added to the children array of the appropriate parent.
const arr = [
{id: 1},
{id: 2, parentId: 1},
{id: 3, parentId: 2},
{id: 4, parentId: 3},
{id: 5, parentId: 2},
{id: 6, parentId: 1},
]
let map = {};
arr.forEach((el, i) => {
map[el.id] = i;
el.children = [];
});
let root = [];
arr.forEach(el => {
if (!el.parentId) {
root.push(el);
} else {
arr[map[el.parentId]].children.push(el);
}
});
console.log(root);

Take an object for keeping the reference to the children.
var data = [{ id: 1 }, { id: 2, parentId: 1 }, { id: 3, parentId: 2 }, { id: 4, parentId: 3 }, { id: 5, parentId: 2 }, { id: 6, parentId: 1 }],
tree = function (data, root) {
var o = {};
data.forEach(function (a) {
if (o[a.id] && o[a.id].children) {
a.children = o[a.id].children;
}
o[a.id] = a;
o[a.parentId] = o[a.parentId] || {};
o[a.parentId].children = o[a.parentId].children || [];
o[a.parentId].children.push(a);
});
return o[root].children;
}(data, undefined);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

var array = [
{id: 1},
{id: 2, parentId: 1},
{id: 3, parentId: 2},
{id: 4, parentId: 3},
{id: 5, parentId: 2},
{id: 6, parentId: 1},
];
const findParent = (list, parentId) => {
list = list || [];
return list.find(el => el.id === parentId);
}
const findDeepParent = (list, parentId) => {
for(const i = 0; i < list.length; i++) {
const element = list[i];
const parent = findParent(list, parentId);
if (parent) {
return parent
} else if(element.children) {
return findDeepParent(element.children, parentId);
};
}
}
const objs = array.reduce((result, obj) => {
let found = false;
if (obj.parentId) {
var parent = findDeepParent(result, obj.parentId)
if (parent) {
parent.children = parent.children || [];
parent.children.push(obj);
found = true;
}
}
if(!found) {
result.push(obj);
}
return result;
}, []);
console.log(objs);
This solve your problem

Related

display entire array after moving values of it to another array

I have an array containing objects where there is a rpId key in some of the objects. The goal is to separate/move the objects that return undefined to a separate array and remove them out of the first array.
e.g.:
results = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
goal: results = [{id: 2, rpId: 1076}, {id: 4, rpId: 303}] and stations = [{id: 1}, {id: 3}]
My current approach can be seen below. As visible, I get a wrong array1 because it contains an object with a rpId, plus array2 returns the keys of the object and I'd like to read the entire object, not just the "undefined" of the key.
const array1 = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
const array2 = [];
const mapping = array1.map((e) => e.rpId);
console.log("mapping",mapping);
mapping.forEach(function(elem, index){
elem === undefined ? array2.push(elem) && array1.splice(index, elem === undefined) && console.log(elem): console.log("defined", elem);
}),
console.log("1", array1); // [{ id: 2, rpId: 1076 }, { id: 3 }]
console.log("2", array2); // [undefined, undefined]
Just check if the rpId property is undefined in each element.
const array1 = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
const array2 = [];
array1.forEach(function(elem, index){
if(elem.rpId === undefined)
array2.push(elem) && array1.splice(index, 1)
});
console.log(array1);
console.log(array2);
One can also use Array#filter or push elements into two separate arrays based on the condition for better performance.
const array1 = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
const yes = [], no = [];
array1.forEach(elem=>(elem.rpId!==undefined?yes:no).push(elem));
console.log(yes);
console.log(no);
You can use filter too:
let results = [
{ id: 1 },
{ id: 2, rpId: 1076 },
{ id: 3 },
{ id: 4, rpId: 303 },
];
const stations = results.filter((c) => !c.rpId);
results = results.filter((c) => c.rpId);
console.log("stations", stations);
console.log("results", results);
const GiveItACreativeName = (arr) => {
const result = []
const stations = []
arr.forEach((el) => {
if('rpId' in el) result.push(el);
else stations.push(el);
});
return {result, stations}
}
console.log(
GiveItACreativeName([{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}])
);

Update array of objects with array of objects

My apologies if this has been addressed before, but I couldn't get it to work with anything I found.
Assume I have 2 arrays - arr1, arr2. I want to update the objects in arr1 if the the property id matches in arr1 and arr2. Objects that exist in arr2 but not in arr1 - meaning the property id does not exist in arr1 - should be pushed to arr1.
Example:
let arr1 = [
{id: 0, name: "John"},
{id: 1, name: "Sara"},
{id: 2, name: "Domnic"},
{id: 3, name: "Bravo"}
]
let arr2 = [
{id: 0, name: "Mark"},
{id: 4, name: "Sara"}
]
# Expected Outcome
let outcome = [
{id: 0, name: "Mark"},
{id: 1, name: "Sara"},
{id: 2, name: "Domnic"},
{id: 3, name: "Bravo"},
{id: 4, name: "Sara"}
]
You can use reduce and find for this:
const arr1 = [
{id: 0, name: "John"},
{id: 1, name: "Sara"},
{id: 2, name: "Domnic"},
{id: 3, name: "Bravo"}
];
const arr2 = [
{id: 0, name: "Mark"},
{id: 4, name: "Sara"}
];
arr2.reduce((res, item) => {
const existingItem = res.find(x => x.id === item.id);
if (existingItem) { existingItem.name = item.name; }
else { res.push(item); }
return res;
}, arr1);
console.log(arr1);
You could do this:
let arr1 = [
{id: 0, name: "John"},
{id: 1, name: "Sara"},
{id: 2, name: "Domnic"},
{id: 3, name: "Bravo"}
]
let arr2 = [
{id: 0, name: "Mark"},
{id: 4, name: "Sara"}
]
var res = arr1.reduce((acc, elem)=>{
var x = arr2.find(i=>i.id === elem.id);
if(x){
acc.push(x)
}else{
acc.push(elem)
}
return acc
}, []);
console.log(res)
Assuming you want to mutate the objects in arr1 rather than creating new ones, one way to do it would be using for...of to iterate the objects in arr2 and then check if there's already an object with the same id in arr1 using Array.prototype.find():
If there is one, you mutate it with Object.assign.
Otherwise, push the new object to arr1:
const arr1 = [
{ id: 0, name: 'John' },
{ id: 1, name: 'Sara' },
{ id: 2, name: 'Domnic' },
{ id: 3, name: 'Bravo' },
];
const arr2 = [
{ id: 0, name: 'Mark', sometingElse: 123 },
{ id: 2, foo: 'bar' },
{ id: 4, name: 'Sara' },
];
for (const currentElement of arr2) {
let previousElement = arr1.find(el => el.id === currentElement.id);
if (previousElement) {
Object.assign(previousElement, currentElement);
} else {
arr1.push(currentElement);
}
}
console.log(arr1);
.as-console-wrapper {
max-height: 100% !important;
}
if you want to try something different you can use foreach and filter to achieve this
let arr1 = [
{id: 0, name: "John"},
{id: 1, name: "Sara"},
{id: 2, name: "Domnic"},
{id: 3, name: "Bravo"}
]
let arr2 = [
{id: 0, name: "Mark"},
{id: 4, name: "Sara"}]
arr1.forEach(x=>{
arr2.forEach(y=>{
if(x.id==y.id){
x.name=y.name
}
})
})
arr2.filter((a)=>{if(!arr1.some(b=>a.id==b.id)) arr1.push(a)})
console.log(arr1)
You should be able to use Array.prototype.find to sort this out!
let arr1 = [
{id: 0, name: "John"},
{id: 1, name: "Sara"},
{id: 2, name: "Domnic"},
{id: 3, name: "Bravo"}
];
let arr2 = [
{id: 0, name: "Mark"},
{id: 4, name: "Sara"}
];
let updateArrayOfObjects = (arr1, arr2) => {
for (let obj of arr2) {
let item = arr1.find(v => v.id === obj.id);
if (item) item.name = obj.name;
else arr1.push({ ...obj });
}
return arr1;
};
console.log(updateArrayOfObjects(arr1, arr2));

How to turn a one dimensional array into a two dimensional one where the parents can go more than two levels deep?

I have a one dimensional array of objects and each object has an id and an id of its parent. In the initial array I have each element can have at most one child. So if the array looks like this:
{id: 3, parent: 5},
{id: 5, parent: null},
{id: 6, parent: 3},
{id: 1, parent: null},
{id: 4, parent: 7},
{id: 2, parent: 1},
{id: 7, parent: 2}
I need it to look similar to this:
{id: 5, parent: null, children: [
{id: 3, parent: 5},
{id: 6, parent: 3}
]},
{id: 1, parent: null, children: [
{id: 2, parent: 1},
{id: 7, parent: 2},
{id: 4, parent: 7},
]}
I think the way I did it uses too much loops. Is there a better way?
let items = [
{id: 3, parent: 5},
{id: 5, parent: null},
{id: 6, parent: 3},
{id: 1, parent: null},
{id: 4, parent: 7},
{id: 2, parent: 1},
{id: 7, parent: 2}
];
let itemsNew = [];
items = items.map(function(x){
return {id: x.id, parent: x.parent, children: []};
});
// new array with parents only
for(let i=items.length-1; i>=0; i--){
if(items[i].parent == null){
itemsNew.push(items[i]);
items.splice(i, 1);
}
}
for(let i=0; i<itemsNew.length; i++){
let childIndexes = findChildAll(itemsNew[i].id);
// sort the indexes so splicing the array wont misplace them
childIndexes.sort(function(a,b){
return b-a;
});
for(let j=0; j<childIndexes.length; j++){
itemsNew[i].children.push(items[childIndexes[j]]);
items.splice(childIndexes[j], 1);
}
}
// returns an array of indexes of all the element's children and their children
function findChildAll(parentId){
for(let i=0; i<items.length; i++){
if(items[i].parent == parentId){
let resultArray = findChildAll(items[i].id);
// is the result as an array add it to the index
if(resultArray) return [i].concat(resultArray);
// otherwise return just this index
return [i];
}
}
}
console.log(itemsNew);
You could simplify it a lot by utilizing filter:
for(const item of items)
item.children = items.filter(child => child.parent === item.id);
const parents = items.filter(item => !item.parent);
Or if there are lots of nodes, might be benefitial for performance to use a Map:
const itemsByID = new Map(items.map(item => [item.id, item]));
const parents = [];
for(const item of items) {
if(item.parent) {
const parent = itemsByID[item.parent];
if(!parent.children) parent.children = [];
parent.children.push(item);
} else parents.push(item);
}
finding the children could be slow if the array gets big because the function searches always in the full array
const withParent = items.filter((item) => item.parent !== null);
const getDeepIdsWithId = (id) => {
const result = [];
while (true) {
const i = withParent.find((item) => item.parent === id);
if (!i) {
break;
}
id = i.id;
result.push(i);
}
return result;
};
const parent = items.filter((item) => item.parent === null).map((item) => ({...item, children: getDeepIdsWithId(item.id)}));
I think you can take a recursive approach some like this:
const array1 = [{id: 3, parent: 5},
{id: 5, parent: null},
{id: 6, parent: 3},
{id: 1, parent: null},
{id: 4, parent: 7},
{id: 2, parent: 1},
{id: 7, parent: 2}];
const getChild = (origi, fathers) =>
origi.filter(
originEle => originEle.parent && fathers.some(f => f.id === originEle.parent)
)
const getChildren = (originArray, fathers) => {
let childs = getChild(originArray, fathers);
let continueFetching = childs && childs.length > 0;
while (continueFetching) {
const moreChilds = getChild(originArray, childs)
.filter(m => !childs.some(c => c.id === m.id))
if (
moreChilds &&
moreChilds.length > 0
) {
childs = childs.concat(moreChilds);
continueFetching = true;
} else {
continueFetching = false;
}
}
return childs;
}
const result = array1
.filter(
a1 => !a1.parent
)
.map(
aFather => ({
id: aFather.id,
parent: null,
children: getChildren(array1, [aFather])
})
)

Javascript concat and overwrite where element has the same id

Is it possible to concat two arrays with objects and let the second array overwrite the first array where they have the same id:
// array 1
[
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
// array 2:
[
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
// out:
[
{id: 1, name: "newFoo"}, // overwriten by array 2
{id: 2, name: "bar"}, // not changed (from array 1)
{id: 3, name: "baz"}, // not changed (from array 1)
{id: 4, name: "y"}, // added (from array 2)
{id: 5, name: "z"} // added (from array 2)
]
If it is possible I would like to do this without the use of third party libraries
var a = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
var b = [
{id: 1, name: "fooboo"},
{id: 4, name: "bar"},
{id: 5, name: "baz"}
];
/* iterate through each of b, if match found in a, extend with that of a. else push into b ...*/
b.forEach(m => {
var item = a.find(n => n.id === m.id);
if(item) { return Object.assign(item, m); }
a.push(m);
});
console.log(a);
You can do
let arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
let arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
let result = arr1.concat(arr2).reduce((a, b) => {
a[b.id] = b.name;
return a;
},{})
result = Object.keys(result).map(e => {
return {id : e, name : result[e]};
});
console.log(result);
Explanation
I am using the property of objects that they don't keep duplicate keys, so for an array concated together, I reduce it to an object with id as it's key and name as its value, hence overriding all duplicates. In the next step I converted this back into an array.
Check you my solution. There is no "rewrite", i just use a second array as base and don't write value if it has same id.
let a = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
let b = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
];
let duplicateId;
a.forEach(aitem => {
duplicateId = false;
b.forEach(bitem => {
if (aitem.id === bitem.id)
duplicateId = true;
});
if (!duplicateId)
b.push(aitem);
});
console.log(b);
Maybe you can use Object.assign and Object.entries to achieve, lets say:
const arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
const arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
const obj3 = Object.entries(Object.assign({}, ...arr1, arr2))
.map(([prop, value]) => ({[prop]:value}));
Example:
https://jsfiddle.net/0f75vLka/
Another option would be to convert arrays to map with id as key then merge the objects and then convert it back to array.
var arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
var arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
];
function arr2map(arr) {
var map = {};
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
map[item.id] = item;
}
return map;
}
function map2arr(map) {
var arr = [];
for (var i in map) {
arr.push(map[i]);
}
return arr;
}
var arr1m = arr2map(arr1);
var arr2m = arr2map(arr2);
var arr3m = map2arr( Object.assign({}, arr1m, arr2m) );
//output
alert(JSON.stringify(arr3m));

JavaScript format array of objects into nested children

I have an array of objects with parentId and sort values that I'd like to put into an array with nested 'children' and sorted appropriately.
For example, here's the data:
[{
id: 1,
sort: 2,
parentId: null,
name: 'A'
}, {
id: 2,
sort: 1,
parentId: 1,
name: 'A.1'
}, {
id: 3
sort: 2,
parentId: 1,
name: 'A.2'
}, {
id: 4,
sort: 1,
parentId: null,
name: 'B'
}]
The way I'd like to transform this would be such as:
[{
id: 4,
sort: 1,
parentId: null,
name: 'B',
children: []
}, {
id: 1,
sort: 2,
parentId: null,
name: 'A',
children: [{
id: 2,
sort: 1,
parentId: 1,
name: 'A.1'
}, {
id: 3
sort: 2,
parentId: 1,
name: 'A.2'
}]
}]
This is sorted (id 4 being at the top, since sort is 1) and the children are nested and also sorted accordingly.
Any suggestions on a good way to do this? I can recursively loop through to apply children, but not sure how I can maintain sorting on this.
This is a proposal which sorts first and filters after that.
The sorting takes the properties parentId and sort. This is necessary for the next step, because the "filtering" needs a sorted array.
Later the array is filterd with Array#filter(), Here is thisArgs used for referencing nodes for a possible insertation of children.
Edit: Update for unsorted (id/parentId) data.
var array = [{ id: 1, sort: 2, parentId: null, name: 'A' }, { id: 2, sort: 1, parentId: 1, name: 'A.1' }, { id: 3, sort: 2, parentId: 1, name: 'A.2' }, { id: 4, sort: 1, parentId: null, name: 'B' }],
nested;
array.sort(function (a, b) {
return (a.parentId || -1) - (b.parentId || -1) || a.sort - b.sort;
});
nested = array.filter(function (a) {
a.children = this[a.id] && this[a.id].children;
this[a.id] = a;
if (a.parentId === null) {
return true;
}
this[a.parentId] = this[a.parentId] || {};
this[a.parentId].children = this[a.parentId].children || [];
this[a.parentId].children.push(a);
}, Object.create(null));
document.write('<pre>' + JSON.stringify(nested, 0, 4) + '</pre>');
I gave it a try and came back, and there are already other answers, but I'm posting it anyway.
This method modifies the original Array:
var items = [{id: 1,sort: 2,parentId: null,name: 'A'}, {id: 2,sort: 1,parentId: 1,name: 'A.1'}, {id: 3,sort: 2,parentId: 1,name: 'A.2'}, {id: 4,sort: 1,parentId: null,name: 'B'}];
function generate_tree(arr){
var references = {};
arr.sort(function(a,b){
// Save references to each item by id while sorting
references[a.id] = a; references[b.id] = b;
// Add a children property
a.children = []; b.children = [];
if(a.sort > b.sort) return 1;
if(a.sort < b.sort) return -1;
return 0;
});
for(var i=0; i<arr.length; i++){
var item = arr[i];
if(item.parentId !== null && references.hasOwnProperty(item.parentId)){
references[item.parentId].children.push(arr.splice(i,1)[0]);
i--; // Because the current index now contains the next item
}
}
return arr;
}
document.body.innerHTML = "<pre>" + JSON.stringify(generate_tree(items), null, 4) + "</pre>";
I'd create a new data structure, to look like:
{ 1: {
id: 1,
sort: 2,
parentId: null,
name: 'A'
},
2: {
id: 4,
sort: 1,
parentId: null,
name: 'B'
}
}
Things to notice: the new structure is an object, not an array, that contains only the topmost elements in it (the ones with parentId null)
Then, do a for over the original array, and assign new_obj[ orig_arr_curr_elem[parentId] ].children.push(orig_arr_curr_elem)
Then create a new array with the elems from new_obj sort() the (or the children property) however you want
Code that implements the steps 1 and 2 (run this using node):
var util = require('util');
var old_arr = [{
id: 1,
sort: 2,
parentId: null,
name: 'A'
}, {
id: 2,
sort: 1,
parentId: 1,
name: 'A.1'
}, {
id: 3,
sort: 2,
parentId: 1,
name: 'A.2'
}, {
id: 4,
sort: 1,
parentId: null,
name: 'B'
}];
var new_obj = {};
for (var i = 0; i < old_arr.length; i++){
if ( old_arr[i].parentId == null )
new_obj[ old_arr[i].id ] = old_arr[i];
}
for (var i = 0; i < old_arr.length; i++){
if ( old_arr[i].parentId == null ) continue;
new_obj[ old_arr[i].parentId ].children = new_obj[ old_arr[i].parentId ].children || [];
new_obj[ old_arr[i].parentId ].children.push( old_arr[i] );
}
console.log(util.inspect(new_obj, {showHidden: false, depth: null}));

Categories