Implement javascript function using tail recursion - javascript

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');

Related

Why doesn't reassigning the parameter element in forEach work

For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.

How to programatically add new children to a deeply nested object

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)

Javascript - Building conditions dynamically

I created a general function called unique() to remove duplicates from a specific array.
However I'm facing a problem: I want to build the conditions dynamically based on properties that I pass to the function.
Ex: Let's suppose that I want to pass 2 properties, so I want to check these 2 properties before "remove" that duplicated object.
Currently I'm using eval() to build this condition "&&", however according to my search it's really a bad practice.
So, my question is:
What's the proper way to do this kind of thing?
Below is my current code:
function unique(arr, ...props) {
const conditions = [];
for (let prop of props) {
conditions.push(`element['${prop}'] === elem['${prop}']`);
}
const condStr = conditions.join(' && ');
return arr.filter((element, index) => {
const idx = arr.findIndex((elem) => {
return eval(condStr);
});
return idx === index;
});
}
const arr1 = [{
id: 1,
name: 'Josh',
description: 'A description'
}, {
id: 2,
name: 'Hannah',
description: 'A description#2'
}, {
id: 1,
name: 'Josh',
description: 'A description#3'
}, {
id: 5,
name: 'Anyname',
description: 'A description#4'
}];
const uniqueValues = unique(arr1, 'id', 'name');
console.log('uniqueValues', uniqueValues);
This question is a bit subjective as far as implementation details, but the better way if you ask me is to pass in a callback function to hand over to filter.
In doing it this way, you can compose the function anyway you see fit. If you have a complex set of conditions you can use composition to build the conditions in the function before you pass it into your unique function https://hackernoon.com/javascript-functional-composition-for-every-day-use-22421ef65a10
A key to function composition is having functions that are composable. A composable function should have 1 input argument and 1 output value.
The hackernoon article is pretty good and goes much further in depth.
this will return a single function that applies all of your preconditions
function unique(arr, callback) {
return arr.filter(callback);
}
const compose = (...functions) => data =>
functions.reduceRight((value, func) => func(value), data)
unique(
[1, 3, 4, 5 ,7, 11, 19teen]
compose(
(someStateCondition) => { /** return true or false **/ },
(result) => { /** return result === someOtherStateCondition **/}
)
)
Use Array#every to compare all properties inline:
function unique(arr, ...props) {
return arr.filter((element, index) => {
const idx = arr.findIndex(
elem => props.every(prop => element[prop] === elem[prop]);
);
return idx === index;
});
}

RxJS: Asynchronously mutate tree

I have a sequence of objects that I need to asynchronously modify by adding a property to each object:
[{ id: 1 }, { id: 2 }] => [{ id: 1, foo: 'bar' }, { id: 2, foo: 'bar' }]
The synchronous equivalent of this would be:
var xs = [{ id: 1 }, { id: 2 }];
// Warning: mutation!
xs.forEach(function (x) {
x.foo = 'bar';
});
var newXs = xs;
However, in my case I need to append the foo property asynchronously. I would like the end value to be a sequence of objects with the foo property added.
I came up with the following code to solve this problem. In this example I'm just adding a property to each object with a value of bar.
var xs = Rx.Observable.fromArray([{ id: 1 }, { id: 2 }]);
var propertyValues = xs
// Warning: mutation!
.flatMap(function (x) {
return Rx.Observable.return('bar');
});
var newXs =
.zip(propertyValues, function (x, propertyValue) {
// Append the property here
x.foo = propertyValue;
return x;
})
.toArray();
newXs.subscribe(function (y) { console.log(y); });
Is this the best way to solve my problem, or does Rx provide a better means for asynchronously mutating objects in a sequence? I'm looking for a cleaner solution because I have a deep tree that I need to mutate, and this code quickly becomes unweidly:
var xs = Rx.Observable.fromArray([{ id: 1, blocks: [ {} ] }, { id: 2, blocks: [ {} ] } ]);
var propertyValues = xs
// Warning: mutation!
.flatMap(function (x) {
return Rx.Observable.fromArray(x.blocks)
.flatMap(function (block) {
var blockS = Rx.Observable.return(block);
var propertyValues = blockS.flatMap(function (block) {
return Rx.Observable.return('bar');
});
return blockS.zip(propertyValues, function (block, propertyValue) {
block.foo = propertyValue;
return block;
});
})
.toArray();
});
xs
.zip(propertyValues, function (x, propertyValue) {
// Rewrite the property here
x.blocks = propertyValue;
return x;
})
.toArray()
.subscribe(function (newXs) { console.log(newXs); });
Perhaps I shouldn't be performing this mutation in the first place?
Is there a reason you need to create two separate Observables: one for the list you're updating and one for the resulting value?
If you simply perform a .map() over your original list, you should be able to asynchronously update the list and subscribe to the result:
// This is the function that generates the new property value
function getBlocks(x) { ... }
const updatedList$ = Rx.Observable.fromArray(originalList)
// What we're essentially doing here is scheduling work
// to be completed for each item
.map(x => Object.assign({}, x, { blocks: getBlocks(x)}))
.toArray();
// Finally we can wait for our updatedList$ observable to emit
// the modified list
updatedList$.subscribe(list => console.log(list));
To abstract this functionality, I created a helper function that will explicitly schedule work to occur for each item using setTimeout:
function asyncMap(xs, fn) {
return Rx.Observable.fromArray(xs)
.flatMap(x => {
return new Rx.Observable.create(observer => {
setTimeout(() => {
observer.onNext(fn(x));
observer.completed();
}, 0);
});
})
.toArray();
}
You can use this function to schedule work to be completed for each item:
function updateItem(x) {
return Object.assign({}, x, { blocks: getBlocks(x) }
}
var updatedList$ = asyncMap(originalList, updateItem);
updateList$.subscribe(newList => console.log(newList));

Calling same function in multiple object's context?

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);
}

Categories