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.
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 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 have object
var routes = {
"home":{
hash: "/home",
children: {
"just-home": {
hash: "/home/just-home",
children: {...}
},
"sub-homea": {
hash: "/home/sub-homea",
children: {...}
}
},
"contact":{
hash: "/contact",
children: {
"just-contact": {
hash: "/contact/just-contact",
children: {...}
},
"sub-contact": {
hash: "/contact/sub-contact",
children: {...}
}
}
}
How i can set object to just-contact.children when i know for example - that first key is contact, and next just-contat.. ? I need to assign this object dynamically because the known keys will be all time different. So i need use any loop. something like this -
const pathArray = [contact,just-contact]
Object.keys(routes).map(function (item) {
if (routes[item] === pathArray[counter]){
ob = routes[item];
counter++;
}
})
but this will loop only once and it won't go to deep.
UPDATE for more clean explanation -
I will read from path location (localhost:3000/contact/just-contact) the values (contact,just-contact) , which i will save to array (pathArray=[contact,just-contact]), when the location path will be change, the keys in array will be change too. And i need to find children of last key, in this example children of just-contact key
Found simple solution -
pathArray.map(function (item) {
if (obj[item].hash === item){
obj = obj[item].children;
}
})
I have a very deep nested category structure and I am given a category object that can exist at any depth. I need to be able to iterate through all category nodes until I find the requested category, plus be able to capture its parent categories all the way through.
Data Structure
[
{
CategoryName: 'Antiques'
},
{
CategoryName: 'Art',
children: [
{
CategoryName: 'Digital',
children: [
{
CategoryName: 'Nesting..'
}
]
},
{
CategoryName: 'Print'
}
]
},
{
CategoryName: 'Baby',
children: [
{
CategoryName: 'Toys'
},
{
CategoryName: 'Safety',
children: [
{
CategoryName: 'Gates'
}
]
}
]
},
{
CategoryName: 'Books'
}
]
Code currently in place
function findCategoryParent (categories, category, result) {
// Iterate through our categories...initially passes in the root categories
for (var i = 0; i < categories.length; i++) {
// Check if our current category is the one we are looking for
if(categories[i] != category){
if(!categories[i].children)
continue;
// We want to store each ancestor in this result array
var result = result || [];
result.push(categories[i]);
// Since we want to return data, we need to return our recursion
return findCategoryParent(categories[i].children, category, result);
}else{
// In case user clicks a parent category and it doesnt hit above logic
if(categories[i].CategoryLevel == 1)
result = [];
// Woohoo...we found it
result.push(categories[i]);
return result;
}
}
}
Problem
If I return my recursive function it will work fine for 'Art' and all of its children..but since it returns, the category Baby never gets hit and therefor would never find 'Gates' which lives Baby/Safety/Gates
If I do not return my recursive function it can only return root level nodes
Would appreciate any recommendations or suggestions.
Alright, I believe I found a solution that appears to work for my and not sure why my brain took so long to figure it out...but the solution was of course closure.
Essentially I use closure to keep a scoped recursion and maintain my each iteration that it has traveled through
var someobj = {
find: function (category, tree, path, callback) {
var self = this;
for (var i = tree.length - 1; i >= 0; i--) {
// Closure will allow us to scope our path variable and only what we have traversed
// in our initial and subsequent closure functions
(function(){
// copy but not reference
var currentPath = path.slice();
if(tree[i] == category){
currentPath.push({name: tree[i].name, id: tree[i].id});
var obj = {
index: i,
category: category,
parent: tree,
path: currentPath
};
callback(obj);
}else{
if(tree[i].children){
currentPath.push({name: tree[i].name, id: tree[i].id});
self.find(category, tree[i].children, currentPath, callback);
}
}
})(tree[i]);
}
},
/**
* gets called when user clicks a category to remove
* #param {[type]} category [description]
* #return {[type]} [description]
*/
removeCategory: function (category) {
// starts the quest for our category and its ancestors
// category is one we want to look for
// this.list is our root list of categoires,
// pass in an intial empty array, each closure will add to its own instance
// callback to finish things off
this.find(category, this.list, [], function(data){
console.log(data);
});
}
}
Hope this helps others that need a way to traverse javascript objects and maintain parent ancestors.
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);
}