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}));
Related
I've tried modifying some of the similar solutions on here but I keep getting stuck, I believe I have part of this figured out however, the main caveat is that:
Some of the objects have extra keys, which renders my object comparison logic useless.
I am trying to compare two arrays of objects. One array is the original array, and the other array contains the items I want deleted from the original array. However there's one extra issue in that the second array contains extra keys, so my comparison logic doesn't work.
An example would make this easier, let's say I have the following two arrays:
const originalArray = [{id: 1, name: "darnell"}, {id: 2, name: "funboi"},
{id: 3, name: "jackson5"}, {id: 4, name: "zelensky"}];
const itemsToBeRemoved = [{id: 2, name: "funboi", extraProperty: "something"},
{id: 4, name: "zelensky", extraProperty: "somethingelse"}];
after running the logic, my final output should be this array:
[{id: 1, name: "darnell"}, {id: 3, name: "jackson5"}]
And here's the current code / logic that I have, which compares but doesn't handle the extra keys. How should I handle this? Thank you in advance.
const prepareArray = (arr) => {
return arr.map((el) => {
if (typeof el === "object" && el !== null) {
return JSON.stringify(el);
} else {
return el;
}
});
};
const convertJSON = (arr) => {
return arr.map((el) => {
return JSON.parse(el);
});
};
const compareArrays = (arr1, arr2) => {
const currentArray = [...prepareArray(arr1)];
const deletedItems = [...prepareArray(arr2)];
const compared = currentArray.filter((el) => deletedItems.indexOf(el) === -1);
return convertJSON(compared);
};
How about using filter and some? You can extend the filter condition on select properties using &&.
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
console.log(
originalArray.filter(item => !itemsToBeRemoved.some(itemToBeRemoved => itemToBeRemoved.id === item.id))
)
Or you can generalise it as well.
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
function filterIfSubset(originalArray, itemsToBeRemoved) {
const filteredArray = [];
for (let i = 0; i < originalArray.length; i++) {
let isSubset = false;
for (let j = 0; j < itemsToBeRemoved.length; j++) {
// check if whole object is a subset of the object in itemsToBeRemoved
if (Object.keys(originalArray[i]).every(key => originalArray[i][key] === itemsToBeRemoved[j][key])) {
isSubset = true;
}
}
if (!isSubset) {
filteredArray.push(originalArray[i]);
}
}
return filteredArray;
}
console.log(filterIfSubset(originalArray, itemsToBeRemoved));
Another simpler variation of the second approach:
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
const removeSubsetObjectsIfExists = (originalArray, itemsToBeRemoved) => {
return originalArray.filter(item => {
const isSubset = itemsToBeRemoved.some(itemToBeRemoved => {
return Object.keys(item).every(key => {
return item[key] === itemToBeRemoved[key];
});
});
return !isSubset;
});
}
console.log(removeSubsetObjectsIfExists(originalArray, itemsToBeRemoved));
The example below is a reusable function, the third parameter is the key to which you compare values from both arrays.
Details are commented in example
const arr=[{id:1,name:"darnell"},{id:2,name:"funboi"},{id:3,name:"jackson5"},{id:4,name:"zelensky"}],del=[{id:2,name:"funboi",extraProperty:"something"},{id:4,name:"zelensky",extraProperty:"somethingelse"}];
/** Compare arrayA vs. delArray by a given key's value.
--- ex. key = 'id'
**/
function deleteByKey(arrayA, delArray, key) {
/* Get an array of only the values of the given key from delArray
--- ex. delList = [1, 2, 3, 4]
*/
const delList = delArray.map(obj => obj[key]);
/* On every object of arrayA compare delList values vs
current object's key's value
--- ex. current obj[id] = 2
--- [1, 2, 3, 4].includes(obj[id])
Any match returns an empty array and non-matches are returned
in it's own array.
--- ex. ? [] : [obj]
The final return is a flattened array of the non-matching objects
*/
return arrayA.flatMap(obj => delList.includes(obj[key]) ? [] : [obj]);
};
console.log(deleteByKey(arr, del, 'id'));
let ff = [{ id: 1, name: 'darnell' }, { id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' }]
let cc = [{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' }]
let ar = []
let out = []
const result = ff.filter(function(i){
ar.push(i.id)
cc.forEach(function(k){
out.push(k.id)
})
if(!out.includes(i.id)){
// console.log(i.id, i)
return i
}
})
console.log(result)
i want to sort an array of objects having id each object using another array that only has the ids, for example:
object = [
{id: 2, name: carlos},
{id: 1, name: maria},
{id: 4, name: juan},
{id: 3, name: pepe}, //this is the array that i want to be sorted or create a copy to return it
]
[1,2,3,4,5] //this is the array that i will use as reference to sort the first one
the final result should be:
object = [
{id: 1, name: maria},
{id: 2, name: carlos},
{id: 3, name: pepe},
{id: 4, name: juam}, //this is the array that i want to be sorted or create a copy to return it
]
im using two maps, but im always getting and array with undefined:
array_to_be_sorted.map((objects) => {
array_reference.map((id) => {
if (objects.id === id) {
return {...objects}
}
}
}
im using map cause think is the best way for bigs array, because im building a music player, so dont know how many tracks the does the user has
You could use Array.prototype.sort() method to get the result.
const data = [
{ id: 2, name: 'carlos' },
{ id: 1, name: 'maria' },
{ id: 4, name: 'juan' },
{ id: 3, name: 'pepe' },
];
const order = [1, 2, 3, 4, 5];
data.sort((x, y) => order.indexOf(x.id) - order.indexOf(y.id));
console.log(data);
Another solution using Map Object which is faster than the first one.
const data = [
{ id: 2, name: 'carlos' },
{ id: 1, name: 'maria' },
{ id: 4, name: 'juan' },
{ id: 3, name: 'pepe' },
];
const order = [1, 2, 3, 4, 5];
const map = new Map();
order.forEach((x, i) => map.set(x, i));
data.sort((x, y) => map.get(x.id) - map.get(y.id));
console.log(data);
Why not just use Array.prototpye.sort()? It's easy and fast.
const pre = document.querySelector('pre');
let object = [
{id: 2, name: 'carlos'},
{id: 1, name: 'maria'},
{id: 4, name: 'juan'},
{id: 3, name: 'pepe'}
];
const criteria = [1,2,3,4,5];
pre.innerText = 'object:' + JSON.stringify(object, null, 2) + '\n\n';
object.sort((a, b) => {
return criteria[a.id] - criteria[b.id];
});
pre.innerText += 'sorted object:' + JSON.stringify(object, null, 2);
Sort an array using criteria from a second array:
<pre></pre>
You can take advantage of Schwartzian transform and sort data based on another array.
const data = [ { id: 2, name: 'carlos' }, { id: 1, name: 'maria' }, { id: 4, name: 'juan' }, { id: 3, name: 'pepe' }, ],
order = [4, 2, 3, 1, 5],
result = data.map(o => {
const index = order.indexOf(o.id);
return [index, o];
})
.sort((a, b) => a[0] - b[0])
.map(([, o]) => o);
console.log(result);
This question already has answers here:
How do I sort an array of objects based on the ordering of another array?
(9 answers)
Javascript - sort array based on another array
(26 answers)
Closed 4 years ago.
I have two arrays.
itemsArray =
[
{ id: 8, name: 'o'},
{ id: 7, name: 'g'},
{ id: 6, name: 'a'},
{ id: 5, name: 'k'},
{ id: 4, name: 'c'}
]
sortArray = [4,5]
How can i sort itemsArray by sortArray (lodash or pure), but i want to for this:
newArray =
[
{ id: 4, name: 'c'},
{ id: 5, name: 'k'},
{ id: 8, name: 'o'},
{ id: 7, name: 'g'},
{ id: 6, name: 'a'}
]
In a case like this where you want to sort on multiple levels, you need to sort them in descending order of importance inside your sorting function.
In this case we sort regularly on cases where both elements are either in or not in the sorting array.
var itemsArray = [
{ id: 8, name: 'o' },
{ id: 7, name: 'g' },
{ id: 6, name: 'a' },
{ id: 5, name: 'k' },
{ id: 4, name: 'c' }
];
var sortArray = [4, 5];
var sortedItemsArray = itemsArray.sort(function (a, b) {
if (sortArray.includes(a.id) == sortArray.includes(b.id)) { //both or neither are in sort array
return b.id - a.id;
}
else if (sortArray.includes(a.id)) { //only a in sort array
return -1;
}
else { //only b in sort array
return 1;
}
});
console.log(sortedItemsArray);
The above snippet could be expanded in multiple ways, but a popular approach is to separate it into several sorting steps.
var itemsArray = [
{ id: 8, name: 'o' },
{ id: 7, name: 'g' },
{ id: 6, name: 'a' },
{ id: 5, name: 'k' },
{ id: 4, name: 'c' }
];
var sortArray = [4, 5];
function sortId(a, b) {
return b.id - a.id;
}
function sortIdByList(a, b) {
if (sortArray.includes(a.id)) {
return -1;
}
if (sortArray.includes(b.id)) {
return 1;
}
return 0;
}
//TEST
var sortedItemsArray = itemsArray
.sort(sortId)
.sort(sortIdByList);
console.log(sortedItemsArray);
This pattern can be easier to maintain as each step is clearly labeled and the functions can be reused in other sorting cases.
The only downside to this pattern is that you end up iterating over the list multiple times, thus increasing the time to sort. Usually this is a non-issue but on very large lists this can be significant.
Sort by array index only
As the comments points out i misread the question, so my previous two sorting snippets doesn't necessarily give the desired result.
This version sorts only by id index in the sorting array:
var itemsArray = [
{ id: 8, name: 'o' },
{ id: 7, name: 'g' },
{ id: 6, name: 'a' },
{ id: 5, name: 'k' },
{ id: 4, name: 'c' }
];
var sortArray = [4, 5];
//TEST
var sortedItemsArray = itemsArray
.sort(function (a, b) {
//Calculate index value of a
var A = sortArray.indexOf(a.id);
if (A == -1) {
A = sortArray.length;
}
//Calculate index value of b
var B = sortArray.indexOf(b.id);
if (B == -1) {
B = sortArray.length;
}
//Return comparison
return A - B;
});
console.log(sortedItemsArray);
You could take the indices of the array for keeping the relative position and take the special items with a negative index to top for sorting.
Then sort the array by taking the indices.
var array = [{ id: 8, name: 'o' }, { id: 7, name: 'g' }, { id: 6, name: 'a' }, { id: 5, name: 'k' }, { id: 4, name: 'c' }],
sortArray = [4, 5],
indices = array.reduce((r, { id }, i) => (r[id] = i, r), {});
sortArray.forEach((id, i, { length }) => indices[id] = i - length);
array.sort(({ id: a }, { id: b }) => indices[a] - indices[b]);
console.log(array);
console.log(indices);
.as-console-wrapper { max-height: 100% !important; top: 0; }
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
I have an array object:
[
{ id:1, name: 'Pedro'},
{ id:2, name: 'Miko'},
{ id:3, name: 'Bear'},
{ id:4, name: 'Teddy'},
{ id:5, name: 'Mouse'}
]
And I have an array with ids [1, 3, 5],
How can I filter the array object to leave records only with id's from the second one?
If Array.includes() is supported, you can use it with Array.filter() to get the items:
const array = [
{ id: 1, name: 'Pedro'},
{ id: 2, name: 'Miko'},
{ id: 3, name: 'Bear'},
{ id: 4, name: 'Teddy'},
{ id: 5, name: 'Mouse'}
];
const filterArray = [1,3,5];
const result = array.filter(({ id }) => filterArray.includes(id));
console.log(result);
If includes is not supported, you can use Array.indexOf() instead:
var array = [
{ id: 1, name: 'Pedro'},
{ id: 2, name: 'Miko'},
{ id: 3, name: 'Bear'},
{ id: 4, name: 'Teddy'},
{ id: 5, name: 'Mouse'}
];
var filterArray = [1,3,5];
var result = array.filter(function(item) {
return filterArray.indexOf(item.id) !== -1;
});
console.log(result);
Maybe take a Array.prototype.reduce in combination with an Array.prototype.some. This keeps the order of the given array need.
var data = [
{ id: 3, name: 'Bear' },
{ id: 4, name: 'Teddy' },
{ id: 5, name: 'Mouse' },
{ id: 1, name: 'Pedro' },
{ id: 2, name: 'Miko' },
],
need = [1, 3, 5],
filtered = need.reduce(function (r, a) {
data.some(function (el) {
return a === el.id && r.push(el);
});
return r;
}, []);
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');
To keep the order of data you can use Array.prototype.filter:
var data = [
{ id: 3, name: 'Bear' },
{ id: 4, name: 'Teddy' },
{ id: 5, name: 'Mouse' },
{ id: 1, name: 'Pedro' },
{ id: 2, name: 'Miko' },
],
need = [1, 3, 5],
filtered = data.filter(function (a) {
return ~need.indexOf(a.id);
});
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');
In case the data set is small, you are ok with any of the offered solution (ones that use indexOf).
However, these solutions are O(n^2) ones, therefore, given the data set big enough, the lag can become noticeable. In this case, you should build an index prior to selecting elements.
Example:
function filterFast(data, ids) {
var index = ids.reduce(function(a,b) {a[b] = 1; return a;}, {});
return data.filter(function(item) {
return index[item.id] === 1;
});
}
And some benchmarking can be tested here.
You can use the filter method on your Array:
var data = [
{ id:1, name: 'Pedro'},
{ id:2, name: 'Miko'},
{ id:3, name: 'Bear'},
{ id:4, name: 'Teddy'},
{ id:5, name: 'Mouse'}
];
var ids = [1, 3, 5];
var filteredData = filterData(data, 'id', ids[1]);
function filterData(data, prop, values) {
return data.filter(function(item) {
return ~values.indexOf(item[prop]); // ~ returns 0 if indexOf returns -1
});
}
See it in action in this JSFiddle.
Or if you are using jQuery, another option may be:
var arr1 = [1, 3, 5],
arr2 = [{ id: 1, name: 'Pedro' },
{ id: 2, name: 'Miko' },
{ id: 3, name: 'Bear' },
{ id: 4, name: 'Teddy' },
{ id: 5, name: 'Mouse' }],
filtered = $.grep(arr2, function (item) {
if (arr1.indexOf(item.id) > -1) {
return true;
}
});
You can use a for loop on the object array and check hasOwnProperty in another for loop for each ids in [1,3,5] (break out of the loop once an id found). (And break out of the bigger for-loop once all ids are found) If your array object is ordered (e.g. elements sorted from smallest id to biggest id) and so are your list, this solution should be quite efficient.
var c = 0;
for(var i =0; i< objects.length; i++){
for(var v =0; v< list.length; v++)
if(objects[i].hasOwnProperty(list[v])){
delete objects[i]; c++; break;
}
if(c===list.length) break;
}
or use array.splice( i, 1 ); if you don't want an empty slot.
Using filter and indexOf will do the trick:
var filteredArray = dataArray.filter(function(obj) {
return idsArray.indexOf(obj.id) > -1;
});
However, indexOf has linear performance, and it will be called lots of times.
In ES6 you can use a set instead, whose has call has sublinear performance (on average):
var idsSet = new Set(idsArray),
filteredArray = dataArray.filter(obj => idsSet.has(obj.id));
Assuming the toString method of your ids is injective, you can achieve something similar in ES5:
var idsHash = Object.create(null);
idsArray.forEach(function(id) {
idsHash[id] = true;
});
var filteredArray = dataArray.filter(function(obj) {
return idsHash[obj.id];
});