I'm building an array with data received from an API.
The result from the API contains a flat array of all pages on a site. I want to create a new array by making it multidimensional so that a page can have children, which can have their own children etc.
Expected array if I can get this to work:
array =
[{id:1, children:[] } // No children
,{id:2, children:[{id:3, children:[]}]} // One level of children
,{id:4, children:[{id:5, children:[{id:6, children:[]}]}]} // Two levels of children etc...
]
So what I'm doing now when looping through the API data is first checking if the element has children. If it doesn't have any I just append it to the array as it is but with an empty children[] as extra.
If the element has a parent I will get the parentId which I have to search for in my custom array.
if(!element.parent){
array.push(element);
continue;
}
// Now I know this element has a parent. So the fun begins.
var parentId = element.parent.id; // Here I have the parent ID
// Example of a none working append (because I dont know the depth)
for(var i = 0; i < array.length; i++){
if(array[i].id === parentId){
array[i].children.push(element);
}
}
So the question is how to loop through all these possible levels of children to find a match and append it correctly to my custom array?
Perhaps this code sample can lead you in the right direction:
var buildTree = function (arr, parent) {
var result = [];
var subTree = arr.filter((e) => e.parent === parent);
for (var i = 0; i < subTree.length; ++i) {
result[i] = {
...subTree[i],
children: buildTree(arr, subTree[i].id)
}
};
return result;
};
var arr = [{
id: 1,
parent: 0,
name: "item-1"
},
{
id: 2,
parent: 1,
name: "item-1.1"
},
{
id: 3,
parent: 2,
name: "item-1.1.1"
},
{
id: 4,
parent: 2,
name: "item-1.1.2"
},
{
id: 5,
parent: 1,
name: "item-1.2"
},
{
id: 6,
parent: 0,
name: "item-2"
},
];
var result = buildTree(arr, 0);
console.log(result);
The recursive buildTree method is returning each time the subtree from a given parent id (first time is expected to be the root of the tree).
BTW: in recursion, the most important thing you need to care about is, besides what you want your method to do (one level each time), the stop condition for your method (when you won't call it again). In this case when there are no more children (handled by the for loop over the filtered data).
Related
So I have a series of objects that are pulled from an API and inputted into an array, something like such:
array = [
{id: 0, name: "First", relationship: "Friend"},
{id: 1, name: "Second", relationship: "Friend"}
]
The user is allowed to add and remove objects to the list freely (they will appear within a Vue.JS DataTable), and said user is allowed a maximum of 4 objects within the array (lets say 4 "friends")
How should I go about implementing a function that searches the existing array (say, if its populated from the API), and inputs the new object with the corresponding ID that is missing (so if the user deletes the object with the id 2, and adds another, it will search said array with objects, find the missing id 2 slot in the array, and input the object in its place)?
Previously I have gone about it via implement array.find() with conditionals to see if the array contains or does not contain the certain id value, however, it searches through each entry and can end up inserting the same object multiple times. Another method I haven't attempted yet would be having a separate map that contains ids, and then when a user removes an object, having it correspond with the map, and vice versa when adding.
Any suggestions? Thanks
Instead of an array, I'd keep an object in data. Have it keyed by id, like this:
let objects = {
0: { id: 0, name: 'name0', relationship: 'relationship0' },
1: { id: 1, name: 'name1', relationship: 'relationship1' },
}
Integer keys in modern JS will preserve insertion order, so you can think of this object as ordered. The API probably returns an array, so do this...
// in the method that fetches from the api
let arrayFromApi = [...];
this.objects = array.reduce((acc, obj) => {
acc[obj.id] = obj; // insertion order will be preserved
return acc;
}, {});
Your UI probably wants an array, so do this (refer to "array" in the markup):
computed: {
array() {
return Object.values(this.objects);
},
To create a new object, insert it in order, minding the available keys. Note this is a linear search, but with small numbers of objects this will be plenty fast
methods: {
// assumes maxId is const like 4 (or 40, but maybe not 400)
createObject(name, relationship) {
let object = { name, relationship };
for (let i=0; i< maxId; i++) {
if (!this.objects[i]) {
object.id = i;
this.objects[i] = object;
break;
}
}
try this,
let array = [
{id: 0, name: "First", relationship: "Friend"},
{id: 4, name: "Second", relationship: "Friend"},
{id: 2, name: "Second", relationship: "Friend"},
]
const addItem = (item) => {
let prevId = -1
// this is unnecessary if your array is already sorted by id.
// in this example array ids are not sorted. e.g. 0, 4, 2
array.sort((a, b) => a.id - b.id)
//
array.forEach(ob => {
if(ob.id === prevId + 1) prevId++
else return;
})
item = {...item, id: prevId + 1 }
array.splice(prevId+1, 0, item)
}
addItem({name: "x", relationship: "y"})
addItem({name: "a", relationship: "b"})
addItem({name: "c", relationship: "d"})
console.log(array)
You can simply achieve this with the help of Array.find() method along with the Array.indexOf() and Array.splice().
Live Demo :
// Input array of objects (coming from API) and suppose user deleted 2nd id object from the array.
const arr = [
{id: 0, name: "First", relationship: "Friend" },
{id: 1, name: "Second", relationship: "Friend" },
{id: 3, name: "Fourth", relationship: "Friend" }
];
// find the objects next to missing object.
const res = arr.find((obj, index) => obj.id !== index);
// find the index where we have to input the new object.
const index = arr.indexOf(res);
// New object user want to insert
const newObj = {
id: index,
name: "Third",
relationship: "Friend"
}
// Insert the new object into an array at the missing position.
arr.splice(index, 0, newObj);
// Output
console.log(arr);
I have a nested array. Like below:
I want to find the depth of this nested array, which means the child element has most deep nested children.
let arr = [
{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
In this case, the depth is 3, the tiger has 3 level. So the depth is 3
How could i achieve this? I try to use recursive, but don't know how to find the element which
has most nested children.
Thanks in advance.
Assuming that there are no circular references, you could try something like this
let arr = [{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
function count(children) {
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr))
Our function would not work if there were some circular references, so there might be a need to adjust it accordingly. Detecting circular references is a whole ordeal. If nothing is done about it, the function will throw a Maximum call stack size exceeded error.
In order to handle it without any additional functionality implementation you could use already existing native JSON.stringify to do so. The stringify option will throw an exception only if you try to serialize BigInt values which we can handle ourselves or when objects are cyclic, which is excatly what we wanted.
let arr = [{
name: 'tiger',
children: []
}]
function testCircular(arr){
try {
BigInt.prototype.toJSON = function() { return this.toString() } // Instead of throwing, JSON.stringify of BigInt now produces a string
JSON.stringify(arr);
return false;
}
catch (e) {
// will only enter here in case of circular references
return true;
}
}
function count(children) {
if (testCircular(children)) return Infinity;
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr)) // normally counting
arr[0].children = arr; // creates circular reference
console.log(count(arr)) // counting for circular
I have an array of objects and all of them have at least 1 other object with the same ID. I am trying to reduce this array to only return the object with the most recent date. An example array of what I'm dealing with:
var arrObj = [
{id: 1, startDate: 2019-10-09},
{id: 1, startDate: 2019-10-05},
{id: 1, startDate: 2019-09-30},
{id: 2, startDate: 2018-08-05},
{id: 2, startDate: 2018-09-05}
]
Output I am trying to achieve:
var newArrObj = [
{id: 1, startDate: 2019-10-09},
{id: 2, startDate: 2018-09-05}
]
I have found a lot of examples of reducing or removing duplicate objects by ID completely, but I can't seem to find an example of first identifying the objects with the same ID, then reducing by a second property. Unfortunately I keep finding myself creating a lot of nested for loops. Is there an elegant way to do this? I need to be able to do it in ES5 or earlier.
var uniqueIDs = [];
for (a in arrObj) {
if(uniqueIDs.indexOf(arrObj[a].id) != -1) {
uniqueIDs.push(arrObj[a].id);
}
}
var newArrObj = [];
for(b in uniqueIDs) {
var uniqueId = uniqueIDs[b];
var uniqueIdObjs = arrObj.filter(function(x) {
return x.id === uniqueId;
});
for(c in uniqueIdObjs) {
var objDate = uniqueIdObjs[c].startDate;
//This is where I get stuck
}
}
I'd go with your style of first filtering the IDs to uniques, because thats the easy part.
Then you can simply chain array function so extract the last date:
map over the unique IDs so that your returns create a new array at the end.
filter the original array to include only object with the ID we workin on now
sort the temporary array created with all objects of the same ID so that the mostrecent one would be first, and return the first element.
let finalResult = uniqueIDs.map( id => {
return arrayObj.filter( obj => obj.id === id )
.sort((a, b) => new Date(b.startDate) - new Date(a.startDate))[0];
Tada! finalResult is now and array containing only one object with the most recent date for every ID :)
You can sort the array by date, get an array of ids and dedupe it, then map the deduped array to replace them with actual objects from the sorted array, Array.find will return the first matching result :
var arrObj = [
{ id: 1, startDate: "2019-10-09" },
{ id: 1, startDate: "2019-10-05" },
{ id: 1, startDate: "2019-09-30" },
{ id: 2, startDate: "2018-08-05" },
{ id: 2, startDate: "2018-09-05" }
];
// Sort by date
var sorted = arrObj.sort((a, b) => new Date(b.startDate) - new Date(a.startDate));
// dedupe the array of ids
var ids = [...new Set(sorted.map(o => o.id))];
// replace the ids with original objects
var result = ids.map(id => sorted.find(o => o.id === id));
console.log(result);
I have been searching through the internet and cannot find the right word to search so I end up here asking again to all of you seniors in Javascript.
The code below is taken from the answer on these question
I really don't understand how the method or function work.
var list = [{id: 1,title: 'home',parent: null},{id: 2,title: 'about',parent: null},{id: 3,title: 'team',parent: 2},{id: 4,title: 'company',parent: 2} ];
function treeify(list) {
var treeList = [];
var lookup = {};
list.forEach(function(obj) {
obj['children'] = [];
lookup[obj['id']] = obj;
});
console.log(lookup); // [problem number 1]
list.forEach(function(obj) {
if (obj['parent'] != null) {
lookup[obj['parent']]['children'].push(obj);
} else {
treeList.push(obj);
}
});
console.log(treeList); // [problem number 2]
};
treeify(list);
On problem number 1:
It resulted an object that had already a children on each parent which is supposedly I think that, parent should have an empty array children at that moment. How does it work? Please enlightened me.
On problem number 2
treeList already formed the hierarchy tree. How it happen? it didn't even push the lookup variable to the treeList variable? It only pushes the obj with a parent that is equal to null (which is the root parent).
Hoping for your answer.
If you know any blog, article and etc. that may help me understand javascript please don't hesitate to add the link in your answer.
Many Thank you!
var list = [
{id: 1,title: 'home',parent: null},
{id: 2,title: 'about',parent: null},
{id: 3,title: 'team',parent: 2},
{id: 4,title: 'company',parent: 2}
];
treeify(list);
function treeify(list) {
var treeList = []; //local array
var lookup = {}; //local object
// iterate over each element in list array
list.forEach(function(obj) {
// add a children property to each element in the array
// in this case the children property is an array
obj['children'] = [];
// obj['id'] returns 1,2,3,4
// lookup is an object so we use the id of each element in the list array as a key
// first iteration adds key : 1 and value {id:1, title: 'home', parent: null, children: [] }
// second iteration adds key : 2 and value {id:2, title: 'about', parent: null, children: [] }
// ...
lookup[obj['id']] = obj;
});
/*
console.log(lookup) should output
{
1: {id: 1,title: 'home', parent: null, children: []},
2: {id: 2,title: 'about', parent: null, children: []},
3: {id: 3,title: 'team', parent: 2, children: []},
4: {id: 4,title: 'company', parent: 2, children: []}
}
however, if you run the code the lookup object gets modifyed in
the lines below (lookup[obj['parent']]['children'].push(obj);),
therefore, some items in the lookup object will have children elements in its child array
*/
console.log(lookup); // [problem number 1]
list.forEach(function(obj) {
if (obj['parent'] != null) {
// this line modifyes the lookup object at runtime
// obj has a parent, so we add it to the corresponding parents children array using the id
lookup[obj['parent']]['children'].push(obj);
} else {
//only adds an element to the treelist array if its a parent element
treeList.push(obj);
}
});
console.log(treeList);
};
Problem 1:
You are right with your assumption, but the problem is that console.log is logging a reference. Because the lookup object gets changed later on you see these changes. read more
function treeify(list) {
var treeList = [];
var lookup = {};
list.forEach(function(obj) {
obj['children'] = [];
lookup[obj['id']] = obj;
});
console.log(lookup); // Now you're right
};
Problem 2:
All objects in lookup are referenced to list.
lookup[obj['id']] = obj;
Here the childrens get changed.
lookup[obj['parent']]['children'].push(obj);
In treeList are also the same references.
Please note that the following scenario is for the demonstration purposes only.
Lets assume I have a following array of object:
var obj = [{
id: 4345345345,
cat: [{
id: 1,
cat: "test1"
}, {
id: 2,
cat: "test2"
}]
}, {
id: 3453453421,
cat: [{
id: 1,
}, {
id: 2,
}]
}];
My goal is to :
Find an object within an array with #id 4345345345, add property selected : true to it
Then within this object with #id 4345345345, find cat with #id 2, add property
selected : true to it
The below works, however should my array have 1000+ objects it's feels somehow wasteful, can you please suggest any cleaner/clever solution ( possible using underscore)?
for (var i = 0; i < obj.length; i++) {
var parent = obj[i];
if (parent.id === 4345345345) {
parent.selected = true;
for (var j = 0; j < parent.cat.length; j++) {
var sub = parent.cat[j];
if(sub.id === 2) {
sub.selected = true;
}
};
}
};
Here are a few approaches I can think of
1) change your data structure to use the id's as the key. ie.
4345345345 :
cat: { 1 :{
cat: "test1"
}, 2 : {
cat: "test2"
}}
2) Alternatively you can create a temporary lookup table based on the id to directly look the object actual objects; obviously you would only create the look up table one time, or whenever the data changes. This will bring your runtime from O(n) to O(1).