I have an object structure like so;
{
this.parent = undefined;
this.children = [];
}
All values in children have the same structure as above except that their parent would be would a reference to the object that has it as its child.
How can I easily iterate over all children of children etc but in the child's object context?
I know how I can loop the children for one object
obj.children.forEach(function(child) {
});
But how can I iterate all children of children, when the children could be into 10-20-30 deep heirachy?
Use recursion.
function deepForEach(node, fn) {
fn(node);
node.children.forEach(function(child) {
deepForEach(child, fn);
});
}
deepForEach(obj, function(obj) {
console.log(obj);
});
The way this works becomes evident if we state it in plain English:
If there are no children, just call the callback with the node. (base case)
If there are children, first deal with ourself, and then do this whole procedure for each child.
This type of recursion is called preorder traversal.
We will write a recursive function. Recursive means that it will execute itself again:
function iterate(obj) {
// we will write the parent and the name
console.log(obj.parent + ' | ' + obj.name);
// if it has children
if (obj.children.length) {
// for each child
for (var i = 0, l = obj.children.length; i < l; i++) {
// we will call this function again
arguments.callee(obj.children[i]);
}
}
}
Now if we have an object like this:
var obj = {
name: 'P1',
parent: undefined,
children: [
{
name: 'P2',
parent: 'P1',
children: []
},
{
name: 'P3',
parent: 'P1',
children: [
{
name: 'P4',
parent: 'P3',
children: [
{
name: 'P5',
parent: 'P4',
children: []
}
]
}
]
},
{
name: 'P6',
parent: 'P1',
children: []
}
]
};
We can iterate all over it:
iterate(obj);
FIDDLE DEMO (open your console in browser)
The standard way is to use recurson like icktoofay is suggesting.
Something that is a little more annoying in this kind of processing is how to manage traversal in "chunks" (e.g. if you want to do this in "background" in a javascript program using a timer).
In this case you can use an explicit stack:
function process_tree(root_node,
process_node,
chunk_size,
completion_call)
{
var todo = []; // nodes that need processing
function processOneChunk() {
for (var j=0; todo.length && j<chunk_size; j++) {
var x = todo.pop();
process_node(x);
for (var i=0; i<x.children.length; i++) {
todo.push(x.children[i]);
}
}
if (todo.length) {
setTimeout(processOneChunk, 0);
} else {
completion_call();
}
}
todo.push(root_node);
setTimeout(processOneChunk, 0);
}
Related
Consider the following object, which is composed by an unknown number of deeply nested children.
const state = {
id: 1,
children: [
{
id: 3,
children: [
{
id: 4,
children: []
}
]
},
{
id: 2,
children: []
}
]
}
How can I programatically push a new object to the children array of a node knowing only its id and the array of ids of its parents?
I thought using recursion but I couldn't find a solution that worked. I am also using immutability-helper, so I have tried using Array.reduce() to return an object that looked like this:
const newState = {
children: {
[idxOfNodeToChange]: {
children: {$push: newChildren}
}
}
}
so I could pass it to update() but there I am even more stuck since I would still have to traverse through the accumulator every time to go as deep as needed, and I'm not sure how to do that. Any ideas?
Extra info: I'm using a D3 library for React called VX and this structure is required to build a tree component, so I'm stuck on how to add new nodes programatically.
Here you go. You can use this recursive function to search by id and append data to the found node's children array.
function appendChildToNode(node, nodeId, data) {
// If the node is empty, just return it as is.
if (!node) {
return node;
}
let children;
if (node.id === nodeId) {
// If the node has the id we're searching for,
// append the data to its children.
children = [...node.children, data];
} else {
// Otherwise, apply the function recursively to each of its children
children = node.children.map(childNode => appendChildToNode(childNode, nodeId, data));
}
return { ...node, children };
}
It is immutable and you may use it like this:
const newState1 = appendChildToNode(state, 4, { id: 5, children: [] });
const newState2 = appendChildToNode(state, 2, { id: 5, children: [] });
See it working in the example snippet below.
const state = {
id: 1,
children: [{
id: 3,
children: [{
id: 4,
children: []
}]
},
{
id: 2,
children: []
}
]
};
const newState1 = appendChildToNode(state, 4, {
id: 5,
children: []
});
const newState2 = appendChildToNode(state, 2, {
id: 5,
children: []
});
console.log(state); // Orginal state should not be mutated.
console.log(newState1);
console.log(newState2);
function appendChildToNode(node, nodeId, data) {
// If the node is empty, just return it as is.
if (!node) {
return node;
}
let children;
if (node.id === nodeId) {
// If the node has the id we're searching for,
// append the data to its children.
children = [...node.children, data];
} else {
// Otherwise, apply the function recursively to each of its children
children = node.children.map(childNode => appendChildToNode(childNode, nodeId, data));
}
return { ...node, children };
}
Update: The above function uses ES6 spread syntax to append items. If you need to support older browsers w/o transpilation, you can use this updated version using Object.assign and Array#concat.
function appendChildToNode(node, nodeId, data) {
if (!node) {
return node;
}
var children;
if (node.id === nodeId) {
children = node.children.concat([data]);
} else {
children = node.children.map(childNode => appendChildToNode(childNode, nodeId, data));
}
return Object.assign({}, node, { children });
}
Let's find the node with ID 3 in our state, searching one level deep. Take the children of the current node, and find the node with correct ID within those children:
id = 3
node3 = state.children.find(v => v == id)
In that node, find ID 4. Now we're searching in the children of node 3:
id = 4 // ┌ act on node3!
node4 = node3.children.find(v => v == id)
Fitting that to Array.reduce(), the accumulator is the current node. It starts at the root node state, and then traverses the tree downwards: each time, we traverse the tree one level, using the next ID from a list of IDs. We need the recursion to start at the root of the tree, so the initial value is state, our root node.
If we take the above examples and reduce them:
[3, 4].reduce((acc, x) => acc.children.find(v => v === x), state)
// ids traverse one level start at root node
Unrolling it, this is equivalent to:
(state.children.find(v => v === 3)).children.find(v => v === 4)
The general form becomes:
const recursiveTraversal = ids =>
ids.reduce((acc, x) => acc.children.find(v => v === x), state)
I got the following array:
let arr = [
{
children: [
{
children: [],
current: true //pointer is at the first element with no children
},
{
children: [
{
children: [],
current: false //if the pointer is here, next item should be the next parent`s first child (without children)
},
{
children: [
{
current: false,
children: []
},
{
current: false,
children: []
}
],
},
{
children: [],
current: false
}
]
},
{
children: [],
current: false
},
]
},
{
children: [],
current: false
},
{
children: [],
current: false
},
{
children: [],
current: false
},
];
I am creating an array walker (tree walker) so that the user could walk through it back and forth. So for example, when the user hits forward button, the pointer (current) moves to the next position among children in array, making this element current pointer false and next element current pointer true. If there is no more children in current parent, pointer moves to the next parent and makes current first child there (which does not have its children). User can only move among children which dont have their children (their children element is empty or there is no children at all), a parent with children cannot be selected and made current. My code is as follows:
makeNextQuestionCurrent = (arr, currentChanged = false) => {
arr.some(
(item, index) => {
if(item.children && item.children.length > 1){
if(!currentChanged){
this.makeNextQuestionCurrent(item.children);
}
}else{
if(item.current && !currentChanged){
item.current = false;
if(arr[index + 1]){
arr[index + 1].current = true;
currentChanged = true;
}else{
//some logic should be here...
}
}
}
}
);
}
So my problem begins when I reach the end of children of a parent. When the child is last, I cannot jump to next parent children. Any ideas how to fix it would be welcome. Thank you.
I think you answered your own question:
So my problem begins when I reach the end of children of a parent. When the child is last, I cannot jump to next parent children.
You need a way to back up out of branches of the tree when you are done walking that branch. There are two ways you can do this:
Add a pointer to each child pointing to its parent, OR
Have your tree walker keep track of parents (and their parents) using a stack data structure.
You would go with option 1 if you want to maintain your current strategy of keeping your walking state within the tree itself. But this is a very bad idea:
It clutters up a data structure with program state that should be independent.
It results in complicated code, as yours is.
It is both memory and CPU inefficient.
It only allows one tree walker to operate at a time.
If you decide to disentangle your walker state from your tree data as I suggest, you can go with either option 1 or option 2.
Here is an implementation of an option 2 tree walker:
class TreeWalker {
currentNode
currentChildIdx
parentStack
constructor (treeRoot) {
this.currentNode = treeRoot
this.currentChildIdx = -1
this.parentStack = []
}
next () {
// walk until a leaf node is found or we hit the end (terminal conditions are return stmts inside the loop)
while (true) {
const currentNode = this.currentNode
const currentChildIdx = ++this.currentChildIdx
if (currentNode.children && currentChildIdx < currentNode.children.length) {
// we have more children; advance to the nex
const child = currentNode.children[currentChildIdx]
if (child.children) {
// the next child itself has children; save state at this level, then descend
this.parentStack.push({ node: currentNode, childIdx: currentChildIdx })
this.currentNode = child
this.currentChildIdx = -1
} else {
// the next child is a leaf; return it
return child
}
} else if (this.parentStack.length > 0) {
// no more children; back out to a parent.
let p = this.parentStack.pop()
this.currentNode = p.node
this.currentChildIdx = p.childIdx
} else {
// back at root, all done
return null
}
}
}
previous () {
// I'll leave this one for you.
}
}
TreeWalker assumes a consistent tree structure including having a root node that is the same structure as any other node.
I does not store walk state in the tree, so current: was all removed.
let root = {
val: 'branch a',
children: [
{
val: 'leaf 1'
},
{
val: 'branch b',
children: [
{
val: 'leaf 2'
}
]
},
{
val: 'branch c',
children: [
{
val: 'leaf 3'
}
]
}
]
}
I left some work for you: ;)
previous()
returning the root node if it is also a leaf node.
I have got a flat array representing a tree, and I want to build a nested object using tail recursion.
I've got the following code to run and generate the desired output, but I am not sure if it is a proper implementation of tail recursion.
Please advice :)
const myArray = [
{ id: 'root' },
{ id: 0, parent: 'root' },
{ id: 1, parent: 'root' },
{ id: 2, parent: 0 },
{ id: 3, parent: 1 },
{ id: 4, parent: 2 },
{ id: 5, parent: 1 },
{ id: 6, parent: 4 },
{ id: 7, parent: 0 },
{ id: 8, parent: 0 },
];
function makeNestedTreeFromArray(array, id, children) {
if (children.length <= 0) {
return array.find(entry => entry.id === id);
}
return ({
...array.find(entry => entry.id === id),
children: children.map(child => makeNestedTreeFromArray(
array,
child.id,
array.filter(entry => entry.parent === child.id),
))
});
}
const myTree = makeNestedTreeFromArray(
myArray,
'root',
myArray.filter(entry => entry.parent === 'root'),
);
console.log(myTree);
The basics of a tail recursion is to return the same function with changed parameters. This allows to replace the last stack entry with the new call of the function without increasing the stack size.
The following approach uses a TCO and returns the function call and uses a standard exit condition to return from a recursive function at the top of the function.
The algorithm visits each item only ones and builds a tree which has multiple roots. At the end only the wanted root is returned. This approach works for unsorted data, because for every node, both information of id and parent are used and their relation is preserved.
function getTree(data, root, index = 0, tree = {}) {
var o = data[index];
if (!o) return tree[root];
Object.assign(tree[o.id] = tree[o.id] || {}, o);
tree[o.parent] = tree[o.parent] || {};
tree[o.parent].children = tree[o.parent].children || [];
tree[o.parent].children.push(tree[o.id]);
return getTree(data, root, index + 1, tree);
}
const
data = [{ id: 'root' }, { id: 0, parent: 'root' }, { id: 1, parent: 'root' }, { id: 2, parent: 0 }, { id: 3, parent: 1 }, { id: 4, parent: 2 }, { id: 5, parent: 1 }, { id: 6, parent: 4 }, { id: 7, parent: 0 }, { id: 8, parent: 0 }],
tree = getTree(data, 'root');
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Your function does not have a tail call and it won't under all circumstances, because you call the recursive call more than once: Remember that tail call optimization basically means that the function is turned into a loop, ... which is impossible for this case.
That said, instead of finding all the nested elements recursively and iterating over the array a lot of times, use a id to object Map, then you just need to iterate twice: Once to build up the Map, and a second time to link each element to it's parent. A great implementation of that can be found here.
Here would be a tail-call version (I would just use a loop here though):
function listToTree([el, ...rest], parent = new Map, roots = []) {
if(el.parentID)
parent.get(el.parentID).children.push(el);
else roots.push(el);
parent.set(el.id, el);
el.children = [];
if(!rest.length) return roots;
return listToTree(rest, parent, roots); // A proper tail call: This can be turned into a loop
}
A "tail call" is a call to a function that occurs as the last thing in another function (in particular, any return values are forwarded to the caller).
For example:
function foo() {
...
return bar("hi"); // a tail call to bar
}
Tail recursion means it's a tail call to the function itself, i.e. a recursive tail call:
function foo() {
...
return foo(); // a recursive tail call, or: tail recursion
}
This does not apply to your code because you have
function makeNestedTreeFromArray(array, id, children) {
...
return ({
...
I.e. your function returns a new object, not the result of another function call (let alone a call to itself).
You can optimize the code by reducing the time complexity of your code by grouping the items (parent_id) in an object just once and retrieving it whenever you need the children for that parent instead of searching (find or filter) through it in every recursion.
var listTree = (array, parentId, searchObj)=>{
if(searchObj === undefined) {
// Create the searchObject only once. Reducing time complexity of the code
searchObj = {};
array.forEach(data => {
if(searchObj[data.parent]){
searchObj[data.parent].push(data)
}else {
searchObj[data.parent] = [data];
}
});
}
let children = searchObj[parentId];
// return empty array if no parent is retrieved.
return !children ? [] : children.map(single=>{
// Pass in the same searchObj so the the search filter is not repeated again
single.children = listTree(array, single.id, searchObj)
return single;
})
}
// Run the code
listTree(myArray, 'root');
I'm struggling to merge 2 arrays efficiently in JQuery. Essentially I should merge the 2 arrays but if there is a 'element collision' array1 takes precedence. I think what I am doing is a intersection where array1 has precidence?
What isn't clear to me is if I should use 2 nested $.each() statements to achieve my goal or if I can use $.grep() or $.filter() to achieve my goals in a more efficient manor? Any advice?
Below explains my objective in code:
var customMenuBtns = [
{
name: 'addText',
callback: ...
},
{
name: 'trash',
callback: function() { // My custom trash callback
console.log("Custom trash");
}
}
];
var defMenuBtns = [
{
name: 'addComponent',
callback: ...
},
{
name: 'trash',
callback: ...
}
];
// Merge customMenuBtns and defMenuBtns.
// If both arrays contain a button with the same name; we discard the
// defMenuBtns and keep the customMenuBtns
// So in the above example; after merging we should have
var menuBtns = [
{
name: 'addText',
callback: ...
},
{
name: 'trash',
callback: function() { // My custom trash callback
console.log("Custom trash");
}
},
{
name: 'addComponent',
callback: ...
}
];
A non jquery solution that should work and preserve order. You may want to add some 'check for property name' steps, depending on how confident you are that a name element will exist.
var merge = function (arr1, arr2) {
var i, outObj = {}, keys = {};
for (i = 0; i < arr2.length; i += 1) {
outObj[arr2[i].name] = arr2[i];
keys[arr2[i].name] = [i, 2];
}
for (i = 0; i < arr1.length; i += 1) {
outObj[arr1[i].name] = arr1[i];
keys[arr1[i].name] = [i, 1];
}
keys = Object.keys(keys).sort(function (a, b) {
return keys[a][1] - keys[b][1] || keys[a][0] - keys[b][0];
});
return keys.map(function (x) {return outObj[x];})
};
I think that you need the function $.extends
var menuBtns = $.extend({}, defMenuBtns, customMenuBtns);
Merge the contents of two or more objects together into the first object.
for more information you can check the jquery documentation here
you can use it to merge arrays with object too
I think that here you have a complete example
var customMenuBtns = [
{
name: 'addText',
callback: function(){}
},
{
name: 'addComponent',
callback: function(){}
},
{
name: 'trash',
callback: function() { // My custom trash callback
console.log("Custom trash");
}
}
];
var defMenuBtns = [
{
name: 'addText',
callback: function(){}
},
{
name: 'addComponent',
callback: function(){}
},
{
name: 'trash',
callback: function(){}
}
];
var menuBtns = $.extend([], defMenuBtns, customMenuBtns);
console.log(menuBtns);
I have an array of objects that can be of any length and any depth. I need to be able to find an object by its id and then modify that object within the array. Is there an efficient way to do this with either lodash or pure js?
I thought I could create an array of indexes that led to the object but constructing the expression to access the object with these indexes seems overly complex / unnecessary
edit1; thanks for all yours replies I will try and be more specific. i am currently finding the location of the object I am trying to modify like so. parents is an array of ids for each parent the target object has. ancestors might be a better name for this array. costCenters is the array of objects that contains the object I want to modify. this function recurses and returns an array of indexes that lead to the object I want to modify
var findAncestorsIdxs = function(parents, costCenters, startingIdx, parentsIdxs) {
var idx = startingIdx ? startingIdx : 0;
var pidx = parentsIdxs ? parentsIdxs : [];
_.each(costCenters, function(cc, ccIdx) {
if(cc.id === parents[idx]) {
console.log(pidx);
idx = idx + 1;
pidx.push(ccIdx);
console.log(pidx);
pidx = findAncestorsIdx(parents, costCenters[ccIdx].children, idx, pidx);
}
});
return pidx;
};
Now with this array of indexes how do I target and modify the exact object I want? I have tried this where ancestors is the array of indexes, costCenters is the array with the object to be modified and parent is the new value to be assigned to the target object
var setParentThroughAncestors = function(ancestors, costCenters, parent) {
var ccs = costCenters;
var depth = ancestors.length;
var ancestor = costCenters[ancestors[0]];
for(i = 1; i < depth; i++) {
ancestor = ancestor.children[ancestors[i]];
}
ancestor = parent;
console.log(ccs);
return ccs;
};
this is obviously just returning the unmodified costCenters array so the only other way I can see to target that object is to construct the expression like myObjects[idx1].children[2].grandchildren[3].ggranchildren[4].something = newValue. is that the only way? if so what is the best way to do that?
You can use JSON.stringify for this. It provides a callback for each visited key/value pair (at any depth), with the ability to skip or replace.
The function below returns a function which searches for objects with the specified ID and invokes the specified transform callback on them:
function scan(id, transform) {
return function(obj) {
return JSON.parse(JSON.stringify(obj, function(key, value) {
if (typeof value === 'object' && value !== null && value.id === id) {
return transform(value);
} else {
return value;
}
}));
}
If as the problem is stated, you have an array of objects, and a parallel array of ids in each object whose containing objects are to be modified, and an array of transformation functions, then it's just a matter of wrapping the above as
for (i = 0; i < objects.length; i++) {
scan(ids[i], transforms[i])(objects[i]);
}
Due to restrictions on JSON.stringify, this approach will fail if there are circular references in the object, and omit functions, regexps, and symbol-keyed properties if you care.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_native_JSON#The_replacer_parameter for more info.
As Felix Kling said, you can iterate recursively over all objects.
// Overly-complex array
var myArray = {
keyOne: {},
keyTwo: {
myId: {a: '3'}
}
};
var searchId = 'myId', // Your search key
foundValue, // Populated with the searched object
found = false; // Internal flag for iterate()
// Recursive function searching through array
function iterate(haystack) {
if (typeof haystack !== 'object' || haystack === null) return; // type-safety
if (typeof haystack[searchId] !== 'undefined') {
found = true;
foundValue = haystack[searchId];
return;
} else {
for (var i in haystack) {
// avoid circular reference infinite loop & skip inherited properties
if (haystack===haystack[i] || !haystack.hasOwnProperty(i)) continue;
iterate(haystack[i]);
if (found === true) return;
}
}
}
// USAGE / RESULT
iterate(myArray);
console.log(foundValue); // {a: '3'}
foundValue.b = 4; // Updating foundValue also updates myArray
console.log(myArray.keyTwo.myId); // {a: '3', b: 4}
All JS object assignations are passed as reference in JS. See this for a complete tutorial on objects :)
Edit: Thanks #torazaburo for suggestions for a better code.
If each object has property with the same name that stores other nested objects, you can use: https://github.com/dominik791/obj-traverse
findAndModifyFirst() method should solve your problem. The first parameter is a root object, not array, so you should create it at first:
var rootObj = {
name: 'rootObject',
children: [
{
'name': 'child1',
children: [ ... ]
},
{
'name': 'child2',
children: [ ... ]
}
]
};
Then use findAndModifyFirst() method:
findAndModifyFirst(rootObj, 'children', { id: 1 }, replacementObject)
replacementObject is whatever object that should replace the object that has id equal to 1.
You can try it using demo app:
https://dominik791.github.io/obj-traverse-demo/
Here's an example that extensively uses lodash. It enables you to transform a deeply nested value based on its key or its value.
const _ = require("lodash")
const flattenKeys = (obj, path = []) => (!_.isObject(obj) ? { [path.join('.')]: obj } : _.reduce(obj, (cum, next, key) => _.merge(cum, flattenKeys(next, [...path, key])), {}));
const registrations = [{
key: "123",
responses:
{
category: 'first',
},
}]
function jsonTransform (json, conditionFn, modifyFn) {
// transform { responses: { category: 'first' } } to { 'responses.category': 'first' }
const flattenedKeys = Object.keys(flattenKeys(json));
// Easily iterate over the flat json
for(let i = 0; i < flattenedKeys.length; i++) {
const key = flattenedKeys[i];
const value = _.get(json, key)
// Did the condition match the one we passed?
if(conditionFn(key, value)) {
// Replace the value to the new one
_.set(json, key, modifyFn(key, value))
}
}
return json
}
// Let's transform all 'first' values to 'FIRST'
const modifiedCategory = jsonTransform(registrations, (key, value) => value === "first", (key, value) => value = value.toUpperCase())
console.log('modifiedCategory --', modifiedCategory)
// Outputs: modifiedCategory -- [ { key: '123', responses: { category: 'FIRST' } } ]
I needed to modify deeply nested objects too, and found no acceptable tool for that purpose. Then I've made this and pushed it to npm.
https://www.npmjs.com/package/find-and
This small [TypeScript-friendly] lib can help with modifying nested objects in a lodash manner. E.g.,
var findAnd = require("find-and");
const data = {
name: 'One',
description: 'Description',
children: [
{
id: 1,
name: 'Two',
},
{
id: 2,
name: 'Three',
},
],
};
findAnd.changeProps(data, { id: 2 }, { name: 'Foo' });
outputs
{
name: 'One',
description: 'Description',
children: [
{
id: 1,
name: 'Two',
},
{
id: 2,
name: 'Foo',
},
],
}
https://runkit.com/embed/bn2hpyfex60e
Hope this could help someone else.
I wrote this code recently to do exactly this, as my backend is rails and wants keys like:
first_name
and my front end is react, so keys are like:
firstName
And these keys are almost always deeply nested:
user: {
firstName: "Bob",
lastName: "Smith",
email: "bob#email.com"
}
Becomes:
user: {
first_name: "Bob",
last_name: "Smith",
email: "bob#email.com"
}
Here is the code
function snakeCase(camelCase) {
return camelCase.replace(/([A-Z])/g, "_$1").toLowerCase()
}
export function snakeCasedObj(obj) {
return Object.keys(obj).reduce(
(acc, key) => ({
...acc,
[snakeCase(key)]: typeof obj[key] === "object" ? snakeCasedObj(obj[key]) : obj[key],
}), {},
);
}
Feel free to change the transform to whatever makes sense for you!