Iterative tree serialization function - javascript

Here is a visual representation of the tree I'm working with and the desired string serialization:
Here is a recursive solution:
function* serialize(root) {
if (root.length === 0)
return;
const [x, xs] = root;
yield x;
for (let i = 0; i < xs.length; i++)
yield* serialize(xs[i]);
yield ")";
}
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
console.log(
Array.from(serialize(tree)).join("")); // abe)fk)))c)dg)h)i)j)))
Apparently, it works but isn't stack safe for very deep trees. How can I transform it into its iterative counterpart?
I know that I need a stack for this. But I cant figure out the details. I'm in particular interested in the underlying mechanics of such a transformation.
I managed to create an iterative pre-order traversal, but oddly enough this didn't help with serializing it iteratively:
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
function* preOrderIt(root) {
if (root.length === 0)
return;
const stack = [root];
while (stack.length > 0) {
let [x, xs] = stack.pop();
for (let i = xs.length - 1; i >= 0; i--)
stack.push(xs[i]);
yield x;
}
}
console.log(
Array.from(preOrderIt(tree)).join("")); // abefkcdghij

Your serialization basically adds an extra dummy child to each node, so you have to "visit" it when iterating:
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
function* preOrderIt(root) {
if (root.length === 0)
return;
const stack = [root];
while (stack.length > 0) {
let [x, xs] = stack.pop();
if (x !== ')') // <-
stack.push([')', []]); // <-
for (let i = xs.length - 1; i >= 0; i--)
stack.push(xs[i]);
yield x;
}
}
console.log(
Array.from(preOrderIt(tree)).join(""));

One option would be to link the nodes. That way you can traverse the tree without keeping the position you are in:
const Node = (value, ...children) => {
const node = { value, children };
children.forEach(child => child.parent = node);
if(children.length > 1)
children.reduceRight((curr, prev) => ((prev.next = curr), prev));
return node;
};
How does that help? Well:
function serialize(node) {
let result = node.value;
while(node) {
// Deep first
while(node.children[0]) {
node = node.children[0];
result += node.value;
}
// then right
if(node.next) {
node = node.next;
result += ")" + node.value;
} else {
// and up at the last node
while(node && !node.next) {
node = node.parent;
result += ")";
}
result += ")";
if(node) {
node = node.next;
result += node.value;
}
}
}
return result;
}

A different approach by using an array for the levels and iterating deeper levels first.
function s(root) {
var i = 0,
node,
levels = [[root]],
result = '';
while (i !== -1) {
[node, ...levels[i]] = levels[i];
if (node) {
result += node[0];
if (node[1].length) {
levels[++i] = node[1];
continue;
}
} else {
i--;
}
result += ')';
}
return result.slice(0, -1); remove extra bracket for the outer missing array
}
const
node = (x, ...xs) => ([x, xs]),
tree = node("a", node("b", node("e"), node("f", node("k"))), node("c"), node("d", node("g"), node("h"), node("i"), node("j"))),
result = s(tree);
console.log(result);

Related

Most efficient way to split string into tree structure

I have a dynamic array of the string. Each string represents the node of the tree. If any string has "_", I want to split into each node and store it in another array.
For. e.g.
'0_2_0_0' needs to split into "0", "0_2", "0_2_0", & "0_2_0_0" then store it in the new array.
I've achieved it by using the multiple for loops. I do not think this is the most efficient way of doing it.
let visibleTreeNodes = [];
const treeData = ['0_0', '0_1', '0_2_0_0', '1'];
for (let i = 0; i < treeData.length; i++) {
if (treeData[i].includes('_')) {
const nodesArray = treeData[i].split('_');
for (let i = 0; i < nodesArray.length; i++) {
let node = nodesArray[0];
for (let j = 0; j <= i; j++) {
if (j !== 0) {
node = node + '_' + nodesArray[j];
}
}
if (visibleTreeNodes.indexOf(node) === -1) {
visibleTreeNodes.push(node)
}
}
} else if (visibleTreeNodes.indexOf(treeData[i]) === -1) {
visibleTreeNodes.push(treeData[i])
}
}
console.log(treeData);
console.log(visibleTreeNodes);
All that is left for me is to explain (and remember) the part of building the tree by #Nina Scholz. I'll basically rename the variables and use familiar syntax.
// data can be directories lists
const data = ['0_0', '0_1', '0_2_0_0', '1']
// the classic group by using reduce
var result = data.reduce(function(agg, item) {
// buliding tree path incrementally
var pointer = agg;
item.split('_').forEach(function(part) {
pointer[part] = pointer[part] || {}
pointer = pointer[part];
});
return agg;
}, {});
// result is classic object tree
console.log(result);
// iterate to print desired output:
function iterate(tree, parent) {
Object.keys(tree).forEach(function(key) {
var value = tree[key];
var full = (parent ? parent + "_" : '') + key
console.log(full)
if (typeof value === 'object') {
iterate(value, full)
}
})
}
iterate(result)
.as-console-wrapper {max-height: 100% !important}
You could build a tree first and then get the pathes from the keys.
const
getFlat = object => Object.keys(object).flatMap(k => [
k,
...getFlat(object[k]).map(p => `${k}_${p}`)
]),
data = ['0_0', '0_1', '0_2_0_0', '1'],
result = getFlat(data.reduce((t, s) => {
s.split('_').reduce((o, k) => o[k] ??= {}, t);
return t;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I tried to simplify your equation. I reached this below snippet. It has a forEach and then a while loop inside forEach for every input string.
let visibleTreeNodes = [];
const treeData = ['0_0', '0_1', '0_2_0_0', '1'];
treeData.forEach(x => {
const arr = x.split("_");
let i = 0;
let node = arr[0];
while (i < arr.length) {
if (!visibleTreeNodes.includes(node)) {
visibleTreeNodes.push(node);
}
node = [node, arr[i + 1]].join("_");
i++;
}
});
console.log(treeData);
console.log(visibleTreeNodes);
You are free to figure out any better solution.
Thanks
A "one-liner":
const treeData = ['0_0', '0_1', '0_2_0_0', '1'];
const visibleTreeNodes = [...new Set(treeData.flatMap(
s=>s.split('_').reduce((a, si, i)=> [...a, a[i-1]+'_'+si])
))];// Set used to remove duplicates
console.log(visibleTreeNodes)
though efficiency must be tested - a more concise solution doesn't automatically result in a shorter run time

Trying to solve Lowest common ancestor

var lowestCommonAncestor = function(root, p, q) {
// return the path to the node
let path = []
const search = (node, target) => {
if (node === null) return false
path.push(node)
if (node === target) return true
const leftSearched = search(node.left, target)
if (leftSearched) return true
const rightSearched = search(node.right,target)
if (rightSearched) return true
path.pop()
}
search(root, p)
const pathP = path
path = []
search(root, q)
const pathQ = path
let result
while(pathP.length > 0 && pathQ.length > 0 && pathP[0] === pathQ[0]) {
result = pathP[0]
pathP.shift()
pathQ.shift()
}
return result
};
console.log(lowestCommonAncestor([3,5,1,6,2,0,8,null,null,7,4],5,1));
Iam getting following error message
const leftSearched = search(node.left, target)
^
TypeError: Cannot read property 'left' of undefined
Could someone help me to fix this issue
Leet Code, as some other code challenge sites, will transform the array input (actually the text input having JSON notation) into an instance of TreeNode, and will pass that as argument to the function with your solution code.
When you want to run the solution locally, you'll have to take care of this transformation yourself. For that purpose you could make use of the fromList function -- specifically for binary trees.
NB: you have a bug in your search function. if (node === target) should be if (node.val === target).
// LeetCode template:
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
// Tool to convert array input to a tree:
function fromList(values) {
if (!values) return;
let it = (function* () {
for (let value of values) {
yield value == null ? null : new TreeNode(value);
}
while (true) yield null;
})();
let root = it.next().value;
let nextlevel = [root];
while (nextlevel.length) {
let level = nextlevel;
nextlevel = [];
for (let node of level) {
if (node) {
node.left = it.next().value;
node.right = it.next().value;
nextlevel.push(node.left, node.right);
}
}
}
return root;
}
// Your function
var lowestCommonAncestor = function(root, p, q) {
// return the path to the node
let path = []
const search = (node, target) => {
if (node === null) return false;
path.push(node);
if (node.val === target) return true;
const leftSearched = search(node.left, target);
if (leftSearched) return true;
const rightSearched = search(node.right,target);
if (rightSearched) return true;
path.pop();
}
search(root, p);
const pathP = path;
path = [];
search(root, q);
const pathQ = path;
let result;
while(pathP.length > 0 && pathQ.length > 0 && pathP[0] === pathQ[0]) {
result = pathP[0];
pathP.shift();
pathQ.shift();
}
return result;
};
// Running your solution on some input
let input = [3,5,1,6,2,0,8,null,null,7,4];
// Make the conversion that LeetCode would do
let root = fromList(input);
let lca = lowestCommonAncestor(root,5,1);
// For your own purposes, just print the value of that node:
console.log(lca.val); // 3

Generate random tree using JavaScript

I am working on a project to generate random data structures for testing solutions for DSA problems. I am trying to form an algorithm that generates a random tree data structure that takes in the input of number of test cases and number of nodes. Since I cannot use pointers and references, I'm having trouble figuring out how to do this in javaScript.
So far I managed to get the basics down, however, I'm getting errors in my code
CODE:
const randnumgen = (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
};
function randtreegen(nodes) {
var string = "";
class Tree {
constructor(nodes) {
this.nodes = nodes;
this.adj = [];
}
addEdge(n, w) {
this.adj[n].push(w);
}
removeEdge(n, w) {
this.adj[n].forEach((elem) => {
if (elem === w) {
var index = this.adj[n].indexOf(elem);
if (index !== -1) this.adj[n].splice(index, 1);
}
});
}
isCyclicUtil(nodes, visited, recStack) {
if (visited[nodes] === false) {
visited[nodes] = true;
recStack[nodes] = true;
this.adj[n].forEach((elem) => {
if (!visited[elem] && this.isCyclicUtil(elem, visited, recStack))
return true;
else if (recStack[elem])
return true;
});
}
recStack[nodes] = false;
return false;
}
isCyclic() {
visited = new Array();
recStack = new Array();
for (var i = 0; i < this.nodes; i++) {
visited[i] = false;
recStack[i] = false;
}
for (var j = 0; j < this.nodes; i++) {
if (this.isCyclicUtil(j, visited, recStack))
return true;
}
return false;
}
}
container = new Set();
let t = new Tree(nodes);
for (var i = 1; i < nodes - 1; i++) {
var a = randnumgen(1, nodes);
var b = randnumgen(1, nodes);
var p = [a, b];
t.addEdge(p[0], p[1]);
while (container.has(`${p[0]},${p[1]}`) || t.isCyclic() === true) {
t.removeEdge(p[0], p[1]);
var a = randnumgen(1, nodes);
var b = randnumgen(1, nodes);
var p = [a, b];
t.addEdge(p[0], p[1]);
}
container.add(`${p[0]},${p[1]}`);
}
container.forEach((elem) => {
string += elem + '\n'
});
return string;
}
function treeGen(test_case, tree_nodes) {
var result = "";
while (test_case-- > 0) {
result += randtreegen(tree_nodes) + '\n';
}
return result;
}
const ans = treeGen(1, 5);
document.write(ans);
ERROR
TypeError: Cannot read property 'push' of undefined at /home/cg/root/7217808/main.js:18
this.adj[n].push(w);
My question is:
Is the Logic correct?
How to resolve the error to make it work?
P.S: I referred to this article on GeeksforGeeks.
The main issue is that you have not created the adj entries as empty arrays. So change:
this.adj = [];
To:
this.adj = Array.from({length: nodes}, () => []); // create that many empty arrays
But there are other issues as well:
Some pieces of code expect that nodes are numbered from 1, while other pieces of code expect a numbering starting at 0. As array indexes start from 0, it is more natural to also number your nodes starting from 0.
There are references to an unknown variable n, which should be nodes. NB: It is strange that you choose a plural name for this variable.
When you return true inside a forEach callback, you don't return from the outer function, but only from the forEach callback. This is not what you intended. Solve this by using a for...of loop.
In isCyclic you have a loop on j, but you increment with i++, so this loop will never end. Make it j++
The cycle test is not enough to ensure that your graph is a tree, because in a directed graph you can still have multiple paths between a node A and a node B, without cycles.
The loop in which edges are created needs one more iteration, so let it start from 0.
I would however suggest a slightly different approach for generating a random tree: shuffle all nodes randomly, and let the first node in that shuffled array be the root of the tree. Iterate all the other nodes, and let them be the destinations of new edges. Note that in a tree there is no node in which two edges arrive.
Then you can do a cycle test. I would however do this different too: perform a test before adding the edge. You can get all descendent nodes of the selected b node, and if a is in that set, then you should not create edge a,b.
Here is your adapted code. I removed the parts that are no longer used:
function randnumgen (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
class Tree {
constructor(nodes) {
this.nodes = nodes;
this.adj = Array.from({length: nodes}, () => []);
}
addEdge(n, w) {
this.adj[n].push(w);
}
descendants(node) {
let visited = new Set([node]);
for (let node of visited) {
for (let elem of this.adj[node]) {
if (!visited.has(elem)) visited.add(elem);
}
}
return visited;
}
}
function shuffle(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function randtreegen(nodes) {
let t = new Tree(nodes);
let [root, ...children] = shuffle([...Array(nodes).keys()]);
let edges = [];
let a;
for (let b of children) {
do {
a = randnumgen(0, nodes-1); // make zero based
} while (t.descendants(b).has(a));
t.addEdge(a, b);
edges.push([a, b]);
}
return edges.join("\n");
}
function treeGen(test_case, tree_nodes) {
var result = "";
while (test_case-- > 0) {
result += randtreegen(tree_nodes) + '\n';
}
return result;
}
const ans = treeGen(1, 5);
console.log(ans);

Insert string into sorted array of strings with binary search

I have the following alphabetically sorted array of strings:
let collection = ["ABC", "BCD", "CAB", "FGH", "JKL", "ZKL"];
I want to insert the string "CQW" inside the collection whilst preserving the sorted order but without having to sort the whole array all over again.
So I would like to have ["ABC", "BCD", "CAB", "CQW", "FGH", "JKL", "ZKL"]; after the insertion completed in O(log n) time.
I figured it would be a good idea to calculate the index at which I need to insert the element using binary search.
I have found the following code for binary search which retrieves the index of a string if it is existent inside a collection:
function binarySearch(items, value) {
let startIndex = 0;
let stopIndex = items.length - 1;
let middle = Math.floor((stopIndex + startIndex) / 2);
while (items[middle] != value && startIndex < stopIndex) {
//adjust search area
if (value < items[middle]) {
stopIndex = middle - 1;
} else if (value > items[middle]) {
startIndex = middle + 1;
}
//recalculate middle
middle = Math.floor((stopIndex + startIndex) / 2);
}
// Return -1 if element is not in collection
return (items[middle] != value) ? -1 : middle;
}
let collection = ["ABC", "BCD", "CAB", "FGH", "JKL", "ZKL"];
console.log(binarySearch(collection, "CQW"));
However, I have been struggling with modifying it so that it returns the precise index at which the string needs to be inserted. How can this be modified so that it works? Is binary search the most efficient way to do this?
The middle value should tell you where to put it. Modify the function's return value so it tells you if it's already in the collection as well
function binarySearch(items, value) {
console.log('Searching for '+value)
let startIndex = 0;
let stopIndex = items.length - 1;
let middle = Math.floor((stopIndex + startIndex) / 2);
while (items[middle] != value && startIndex < stopIndex) {
//adjust search area
if (value < items[middle]) {
stopIndex = middle - 1;
} else if (value > items[middle]) {
startIndex = middle + 1;
}
//recalculate middle
middle = Math.floor((stopIndex + startIndex) / 2);
}
// Return -1 if element is not in collection
// return (items[middle] != value) ? -1 : middle;
return {
found: items[middle] == value,
middle: middle
}
}
let collection = ["ABC", "BCD", "CAB", "FGH", "JKL", "ZKL"];
let item = "CQW"
result= binarySearch(collection, item);
console.log(result)
if(!result.found){
console.log('Adding '+item+' at index '+result.middle)
collection.splice(result.middle, 0, item);
}
console.log(collection)
Output
Searching for CQW
{found: false, middle: 3}
Adding CQW at index 3
["ABC", "BCD", "CAB", "CQW", "FGH", "JKL", "ZKL"]
Since you're inserting into an array you are always going to have a worst case of O(n) in just moving values around "after" the insertion (+ an additional n or log n for finding the place to insert the value at). So i would either just append the value at one end of the array and then sort it with insertion sort (since insertion sort is actually one of the faster algorithms for almost-sorted input data).
const insertionSort = (inputArr) => {
const length = inputArr.length;
for (let i = 1; i < length; i++) {
const key = inputArr[i];
let j = i - 1;
while (j >= 0 && inputArr[j] > key) {
inputArr[j + 1] = inputArr[j];
j = j - 1;
}
inputArr[j + 1] = key;
}
return inputArr;
};
let collection = ["ABC", "BCD", "CAB", "FGH", "JKL", "ZKL"];
collection.push("CQW");
console.log(insertionSort(collection));
Or, if you tend to end up with HUGE arrays and need O(n) worst case complexity for insertion; then i would move to an always sorted doubly-linked list instead.
const linkedList = (value, next) => ({prev: null, value, next});
const insert = (node, value) => {
if (node === null) {
return false;
}
if (node.value < value) {
return insert(node.next, value);
}
const newNode = linkedList(value, node);
newNode.prev = node.prev;
newNode.prev.next = newNode;
node.prev = newNode;
return true;
}
const arrayToList = (arr) => arr.reverse().reduce((next, el) => {
const list = linkedList(el, next);
if (next) {
next.prev = list;
}
return list;
}, null);
const printList = (list) => {
const arr = [];
let node = list;
while (node) {
arr.push(node.value);
node = node.next;
}
console.log(arr);
};
const collection = ["ABC", "BCD", "CAB", "FGH", "JKL", "ZKL"];
const list = arrayToList(collection);
insert(list, "CQW");
printList(list);
// Some function that arent't used in the example
// but are very usefull if you decided to use this solution
const get = (list, index) => {
let node = list;
for (let i = 0; node; i++) {
if (i === index) {
return node.value;
}
node = node.next;
}
return null;
}
const set = (list, index, value) => {
let node = list;
for (let i = 0; node; i++) {
if (i === index) {
node.value = value;
return true;
}
node = node.next;
}
return false;
}
const remove = (list, value) => {
if (node === null) {
return false;
}
if (node.value === value) {
node.prev.next = node.next;
node.next.prev = node.prev;
return true;
}
return remove(node.next, value);
}
const getIndex = (list, value) => {
let node = list;
for (let i = 0; node; i++) {
if (node.value === value) {
return i;
}
node = node.next;
}
return -1;
}

How to collect all nodes of a Tree structure, grouped by depth level?

I have a classic Tree-structure with childs and parent node. Now, i would like to collect all nodes grouped by depth starting from the lowest level (i.e. in reverse order), like this:
nodes[
["A4"],
["A3","B3"],
["A2","B2","C2"],
["A1","B1","C1"],
["ROOT"]
];
While getting the depth level by using the recursive traversal approach is very easy, i'm wonder if there is any method to get immediately the depth level during the Tree traversal in a BFS or DFS search.
I'am aware that i could store the depth level during the node insertion, but as i'm doing a lot of insertion and removals, i would prefer to collect the whole structure grouped by level just in one shot.
Also, i haven't any preference to use BDS or DFS at all, both are just fine. Here is my actual code:
function Node(code, parent) {
this.code = code;
this.children = [];
this.parentNode = parent;
}
Node.prototype.addNode = function (code) {
var l = this.children.push(new Node(code, this));
return this.children[l-1];
};
Node.prototype.dfs = function (leafCallback) {
var stack=[this], n, depth = 0;
while(stack.length > 0) {
n = stack.pop();
if(n.children.length == 0) {
if(leafCallback) leafCallback(n, this);
continue;
}
for(var i=n.children.length-1; i>=0; i--) {
stack.push(n.children[i]);
}
depth++; // ???
}
};
var tree = new Node("ROOT");
tree.addNode("A1").addNode("A2").addNode("A3").addNode("A4");
tree.addNode("B1").addNode("B2").addNode("B3");
tree.addNode("C1").addNode("C2");
You can use recursion and passing node and depth as parameters
function Node(code, parent) {
this.code = code;
this.children = [];
this.parentNode = parent;
}
Node.prototype.addNode = function (code) {
var l = this.children.push(new Node(code, this));
return this.children[l-1];
};
let result = [], depth = {};
function dfs(node){
node.depth = 0;
let stack = [node];
while(stack.length > 0){
let root = stack[stack.length - 1];
let d = root.depth;
result[d] = result[d] || [];
result[d].push(root.code);
stack.length--;
for(let element of root.children){
element.depth = root.depth + 1;
stack.push(element);
}
}
}
var tree = new Node("ROOT");
tree.addNode("A1").addNode("A2").addNode("A3").addNode("A4");
tree.addNode("B1").addNode("B2").addNode("B3");
tree.addNode("C1").addNode("C2");
dfs(tree);
console.log(result.reverse());
It is possible to write this in a recursive way which would benefit from tail optimisation
function reduceTree(tree) {
const getCode = n => n.code;
const _reduce = (level = [tree], acc = [[getCode(tree)]], depth = 1) => {
const children = level.reduce((a, e) => a.concat(e.children), []);
if (!children.length) {
return acc;
}
acc[depth] = children.map(getCode);
return _reduce(children, acc, depth + 1);
};
return _reduce().reverse();
}
reduceTree(tree);
/*
[
["A4"],
["A3", "B3"],
["A2", "B2", "C2"],
["A1", "B1", "C1"],
["ROOT"]
]
*/
That's it - thanks to marvel308 for pointing out that there is required an additional helper node.depth
function Node(code, parent) {
this.code = code;
this.depth = -1;
this.children = [];
this.parentNode = parent;
}
Node.prototype.dfs= function() {
var result = [], stack = [];
this.depth = 0;
stack.push(this);
while(stack.length > 0) {
var n = stack[stack.length - 1], i = n.depth;
if(!result[i]) result.push([]);
result[i].push(n); /* get node or node.code, doesn't matter */
stack.length--;
var children = n.children;
/* keep the original node insertion order, by looping backward */
for(var j = n.children.length - 1; j >= 0; j--) {
var c = children[j];
c.depth = n.depth + 1;
stack.push(c);
}
}
return result.reverse(); /* return an array */
};

Categories

Resources