I'm writing a BST in JS and trying to concatenate a string passed into a member function without success. console.log() works but it automatically starts a new line for each entry.
"use strict";
class Node {
constructor(dt) {
this.data = dt;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
}
insert(parent, key) {
if (!this.root) return this.root = new Node(key);
else {
if (key < parent.data) {
if (!parent.left) {
parent.left = new Node(key);
} else {
this.insert(parent.left, key);
}
} else if (key > parent.data) {
if (!parent.right) {
parent.right = new Node(key);
} else {
this.insert(parent.right, key);
}
}
}
}
// Doesn't work
printIN(parent, string) {
if (parent) {
this.printIN(parent.left);
console.log(parent.data);
string += " " + parent.data;
// return string += " " + parent.data;
this.printIN(parent.right);
}
return string;
}
// This works.
// printIN(parent) {
// if (parent) {
// this.printIN(parent.left);
// console.log(parent.data);
// this.printIN(parent.right);
// }
// }
}
let treeA = new BST();
let tree = null;
tree = treeA.insert(treeA.root, 5);
tree = treeA.insert(treeA.root, 7);
tree = treeA.insert(treeA.root, 3);
tree = treeA.insert(treeA.root, 14);
let string = [""];
string = treeA.printIN(treeA.root, string);
console.log();
console.log(string);
// treeA.printIN(treeA.root);
I want to print out the numbers on one single line, instead of them starting on a new line each time. I thought using string concatenation is the logical solution, but I can't seem to make it work.
// Doesn't work
printIN(parent, string) {
if (parent) {
this.printIN(parent.left);
console.log(parent.data);
string += " " + parent.data;
// return string += " " + parent.data;
this.printIN(parent.right);
}
return string;
Another answer gave you a solution to your string problem. The snippet below has an alternative. But more importantly, it offers some significant clean-up to your OOP approach.
There are two main reasons this is necessary.
First, you have member functions of your object, which still require the object to be passed to them. That's redundant and confusing. Any OOP function which doesn't make reference to this should be a static function. But if you're going to do OOP (I might argue against it in fact), then you shouldn't use static functions as though they were methods. Thus insert and printIN should not have the parent parameter.
Second, you are dealing with a recursive structure. Nodes of a tree should have the same design as the tree itself. This makes all sorts of things easier. But you have a class Node and one called BST. And BST then has a root node, which should then be recursive. There is simply no reason for this that I can see. A BST by nature is a root node, with a data value (usually, although we can make that optional as you do) and potentially left and right nodes that are themselves BSTs.
Fixing the code to work like this simplifies it substantially. Here is one implementation:
class BST {
constructor(data) {
this.data = data;
}
insert(key) {
if (!this.data) {
this.data = key;
} else {
if (key < this.data) {
if (!this.left) {
this.left = new BST(key);
} else {
this.left.insert(key);
}
} else if (key > this.data) {
if (!this.right) {
this.right = new BST(key);
} else {
this.right.insert(key);
}
}
}
}
printIN() {
return (this.left ? this.left.printIN() + ' ' : '') +
this.data +
(this.right ? ' ' + this.right.printIN() : '');
}
}
let treeA = new BST();
treeA.insert(5);
treeA.insert(7);
treeA.insert(3);
treeA.insert(14);
console.log(treeA.printIN());
There is no Node class here. Everything is done in BST. Moreover, BST objects don't need a root node and the methods are simpler.
I would note, though that printIN is less useful than another function which we could use the same way:
toArray() {
return [
...(this.left ? this.left.toArray() : []),
this.data,
...(this.right ? this.right.toArray() : [])
];
}
which would allow
console.log(treeA.toArray()); //=> [3, 5, 7, 14]
and you could get your string just by joining that array with a space. This function strikes me as much more useful than the printIN one you have.
We could also add a static function:
static fromArray(xs) {
return xs .reduce (function (tree, x) {
tree.insert(x)
return tree
}, new BST())
}
that would allow us to write
tree = BST .fromArray ([8, 6, 7, 5, 3, 0, 9]);
tree .printIN() //=> [0, 3, 5, 6, 7, 8, 9]
I mentioned that I wouldn't even do this with OOP. But I don't do much that way these days, preferring functional approaches. So, if you're curious, here's another take on the problem altogether, using just functions, and trees that aren't modified when you do an insert, but instead return new trees:
const bst = (xs) =>
xs .reduce ((tree, x) => bstInsert (x, tree), {})
const bstInsert = (x, {left, data, right} = {}) =>
data == undefined
? {data: x}
: x < data
? {data, left: bstInsert (x, left), ...(right ? {right} : {})}
: {data, ...(left ? {left} : {}), right: bstInsert (x, right)}
const inorder = ({left, data, right} = {}) => [
...(left ? inorder(left) : []),
data,
...(right ? inorder(right) : [])
]
inorder (bst ([8, 6, 7, 5, 3, 0, 9])) //=> [0, 3, 5, 6, 7, 8, 9]
I'm not recommending that technique, specifically, but FP can be a powerful alternative to OOP.
Try this technique.
printIN(parent, result = []) {
if (parent) {
this.printIN(parent.left, result);
result.push(parent.data)
this.printIN(parent.right, result);
}
return result;
}
Pass, as a second argument, the array of strings that will serve as the resulting list of node values as per the in-order bst traversal.
Use a default value for this argument of [] then, if omitted, one will be created, but when recursing, you can pass the existing array in. When you visit a node, push the value into the array. Call printIN on the root node and do not provide the result argument (a new array will be created and returned).
If you want to log all on one line, use Array.prototype.join to transform the array of strings into a single comma-separated string, and log that.
Working example:
class Node {
constructor(dt) {
this.data = dt;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
}
insert(parent, key) {
if (!this.root) return this.root = new Node(key);
else {
if (key < parent.data) {
if (!parent.left) {
parent.left = new Node(key);
} else {
this.insert(parent.left, key);
}
} else if (key > parent.data) {
if (!parent.right) {
parent.right = new Node(key);
} else {
this.insert(parent.right, key);
}
}
}
}
printIN(parent, result = []) {
if (parent) {
this.printIN(parent.left, result);
result.push(parent.data)
this.printIN(parent.right, result);
}
return result;
}
}
let treeA = new BST();
let tree = null;
tree = treeA.insert(treeA.root, 5);
tree = treeA.insert(treeA.root, 7);
tree = treeA.insert(treeA.root, 3);
tree = treeA.insert(treeA.root, 14);
console.log(treeA.printIN(treeA.root).join());
Related
how to return to a binary tree multiple nodes in a array or
a set
simple solution
There are a few issues in your code:
It expects internal nodes to have exactly two children. You should make use of the method getNumberOfChildren
path shouldn't be a global variable, as that will pollute any further call of getPaths. I would suggest to use a generator function, so that the caller can take care of putting the results in their own array variable.
Obviously root.getValue() === pathsToFind is never going to be true as pathsToFind is an array, and the root's value is an integer. You can use includes, but that will bring a bad time complexity when the array is long. So I'd suggest to turn that array into a Set.
It wasn't explained in the question, but it seems like you don't want to include a path to a node, when that node is on a path to another node that needs to be included. In other words, when the array of values has pairs where one is the ancestor of the other, the ancestor can be ignored.
Here is how I would suggest to implement it (with generator):
function* generatePaths(root, targetSet) {
if (!root) return;
const val = root.getValue();
let foundPathsWithThisValue = false;
for (let child = 0; child < root.getNumberOfChildren(); child++) {
for (const path of generatePaths(root.getChild(child), targetSet)) {
path.push(val);
yield path;
foundPathsWithThisValue = true;
}
}
if (!foundPathsWithThisValue && targetSet.has(val)) {
yield [val];
}
}
function getPaths(root, targetValues) {
return Array.from(generatePaths(root, new Set(targetValues)), path =>
path.reverse().join("->")
);
}
// No change below
class Node {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(node) {
this.children.push(node);
}
getValue() {
return this.value;
}
getChild(i) {
return this.children[i];
}
getNumberOfChildren() {
return this.children.length;
}
print(indent = 0) {
console.log("-".repeat(indent), this.value);
for (const child of this.children) {
child.print(indent + 4);
}
}
}
const root = new Node(0);
const leftRoot = new Node(10);
const rightRoot = new Node(500);
root.addChild(leftRoot);
root.addChild(rightRoot);
const leftChild = new Node(150);
const rightChild = new Node(250);
leftRoot.addChild(leftChild);
leftRoot.addChild(rightChild);
const leftleftChild = new Node(200);
leftChild.addChild(leftleftChild);
const rightrightChild = new Node(300);
rightChild.addChild(rightrightChild);
// will output [ '0 -> 10 -> 150 -> 200', '0 -> 500' ]
console.log(getPaths(root, [150, 200, 500]));
You are free to use your accessors instead of root.children. But I guess it is clearer like this, at least for the answer.
Be careful, your property 'root' has the same name as the global variable root
function getPaths(root, pathsToFind) {
let match = false;
if (root === null) return [];
if (pathsToFind.includes(root.getValue())) {
match = true;
}
const pathChildren = root.children.map(child => getPaths(child, pathsToFind))
.filter(child => child.length > 0)
.map(value => `${root.getValue()} -- ${value}`);
if (pathChildren.length > 0) {
return pathChildren
} else {
return match ? [`${root.getValue()}`] : []
}
}
A SkipList is a probabilistic data structure used, at least in part, for implementing an ordered key/value map. It is arranged in levels where higher levels skip nodes (the higher up, the more it skips), and the lowest level contains the nodes. As far as I have read, each level is implemented as a linked list, possibly a doubly linked list. And for some reason, skip lists are better for concurrency because in a multithreaded environment, they can be implemented without locks to enable fast/optimal performance as compared to say red/black trees or B+trees.
Here we have a "proper skip list" implemented in JavaScript, copied here for reference. The link has a nice suite of tests to show how it works.
const DEFAULT_STACK_UP_PROBABILITY = 0.25;
class ProperSkipList {
constructor(options) {
options = options || {};
this.stackUpProbability = options.stackUpProbability || DEFAULT_STACK_UP_PROBABILITY;
this.updateLength = options.updateLength !== false;
this.typePriorityMap = {
'undefined': 0,
'object': 1,
'number': 2,
'bigint': 2,
'string': 3
};
this.clear();
}
clear() {
let headNode = {
prev: null
};
let tailNode = {
next: null
};
this.head = {
isHead: true,
key: undefined,
value: undefined,
nodes: [headNode]
};
this.tail = {
isTail: true,
key: undefined,
value: undefined,
nodes: [tailNode]
};
headNode.next = tailNode;
tailNode.prev = headNode;
headNode.group = this.head;
tailNode.group = this.tail;
this.length = this.updateLength ? 0 : undefined;
}
upsert(key, value) {
let {matchingNode, prevNode, searchPath} = this._searchAndTrack(key);
if (matchingNode) {
let previousValue = matchingNode.group.value;
matchingNode.group.value = value;
return previousValue;
}
// Insert the entry.
let newNode = {
prev: prevNode,
next: prevNode.next
};
let newGroup = {
key,
value,
nodes: [newNode]
};
newNode.group = newGroup;
prevNode.next = newNode;
newNode.next.prev = newNode;
// Stack up the entry for fast search.
let layerIndex = 1;
while (Math.random() < this.stackUpProbability) {
let prevLayerNode = searchPath[layerIndex];
if (!prevLayerNode) {
let newHeadNode = {
prev: null,
group: this.head
};
let newTailNode = {
next: null,
group: this.tail
};
newHeadNode.next = newTailNode;
this.head.nodes.push(newHeadNode);
newTailNode.prev = newHeadNode;
this.tail.nodes.push(newTailNode);
prevLayerNode = newHeadNode;
}
let newNode = {
prev: prevLayerNode,
next: prevLayerNode.next,
group: newGroup
};
prevLayerNode.next = newNode;
newNode.next.prev = newNode;
newGroup.nodes.push(newNode);
layerIndex++;
}
if (this.updateLength) this.length++;
return undefined;
}
find(key) {
let {matchingNode} = this._search(key);
return matchingNode ? matchingNode.group.value : undefined;
}
has(key) {
return !!this.find(key);
}
_isAGreaterThanB(a, b) {
let typeA = typeof a;
let typeB = typeof b;
if (typeA === typeB) {
return a > b;
}
let typeAPriority = this.typePriorityMap[typeA];
let typeBPriority = this.typePriorityMap[typeB];
if (typeAPriority === typeBPriority) {
return a > b;
}
return typeAPriority > typeBPriority;
}
// The two search methods are similar but were separated for performance reasons.
_searchAndTrack(key) {
let layerCount = this.head.nodes.length;
let searchPath = new Array(layerCount);
let layerIndex = layerCount - 1;
let currentNode = this.head.nodes[layerIndex];
let prevNode = currentNode;
while (true) {
let currentNodeGroup = currentNode.group;
let currentKey = currentNodeGroup.key;
if (!currentNodeGroup.isTail) {
if (this._isAGreaterThanB(key, currentKey) || currentNodeGroup.isHead) {
prevNode = currentNode;
currentNode = currentNode.next;
continue;
}
if (key === currentKey) {
let matchingNode = currentNodeGroup.nodes[0];
searchPath[layerIndex] = matchingNode;
return {matchingNode, prevNode: matchingNode.prev, searchPath};
}
}
searchPath[layerIndex] = prevNode;
if (--layerIndex < 0) {
return {matchingNode: undefined, prevNode, searchPath};
}
currentNode = prevNode.group.nodes[layerIndex];
}
}
_search(key) {
let layerIndex = this.head.nodes.length - 1;
let currentNode = this.head.nodes[layerIndex];
let prevNode = currentNode;
while (true) {
let currentNodeGroup = currentNode.group;
let currentKey = currentNodeGroup.key;
if (!currentNodeGroup.isTail) {
if (this._isAGreaterThanB(key, currentKey) || currentNodeGroup.isHead) {
prevNode = currentNode;
currentNode = currentNode.next;
continue;
}
if (key === currentKey) {
let matchingNode = currentNodeGroup.nodes[0];
return {matchingNode, prevNode: matchingNode.prev};
}
}
if (--layerIndex < 0) {
return {matchingNode: undefined, prevNode};
}
currentNode = prevNode.group.nodes[layerIndex];
}
}
findEntriesFromMin() {
return this._createEntriesIteratorAsc(this.head.nodes[0].next);
}
findEntriesFromMax() {
return this._createEntriesIteratorDesc(this.tail.nodes[0].prev);
}
minEntry() {
let [key, value] = this.findEntriesFromMin().next().value;
return [key, value];
}
maxEntry() {
let [key, value] = this.findEntriesFromMax().next().value;
return [key, value];
}
minKey() {
return this.minEntry()[0];
}
maxKey() {
return this.maxEntry()[0];
}
minValue() {
return this.minEntry()[1];
}
maxValue() {
return this.maxEntry()[1];
}
_extractNode(matchingNode) {
let nodes = matchingNode.group.nodes;
for (let layerNode of nodes) {
let prevNode = layerNode.prev;
prevNode.next = layerNode.next;
prevNode.next.prev = prevNode;
}
if (this.updateLength) this.length--;
return matchingNode.group.value;
}
extract(key) {
let {matchingNode} = this._search(key);
if (matchingNode) {
return this._extractNode(matchingNode);
}
return undefined;
}
delete(key) {
return this.extract(key) !== undefined;
}
findEntries(fromKey) {
let {matchingNode, prevNode} = this._search(fromKey);
if (matchingNode) {
return {
matchingValue: matchingNode.group.value,
asc: this._createEntriesIteratorAsc(matchingNode),
desc: this._createEntriesIteratorDesc(matchingNode)
};
}
return {
matchingValue: undefined,
asc: this._createEntriesIteratorAsc(prevNode.next),
desc: this._createEntriesIteratorDesc(prevNode)
};
}
deleteRange(fromKey, toKey, deleteLeft, deleteRight) {
if (fromKey == null) {
fromKey = this.minKey();
deleteLeft = true;
}
if (toKey == null) {
toKey = this.maxKey();
deleteRight = true;
}
if (this._isAGreaterThanB(fromKey, toKey)) {
return;
}
let {prevNode: fromNode, searchPath: leftSearchPath, matchingNode: matchingLeftNode} = this._searchAndTrack(fromKey);
let {prevNode: toNode, searchPath: rightSearchPath, matchingNode: matchingRightNode} = this._searchAndTrack(toKey);
let leftNode = matchingLeftNode ? matchingLeftNode : fromNode;
let rightNode = matchingRightNode ? matchingRightNode : toNode.next;
if (leftNode === rightNode) {
if (deleteLeft) {
this._extractNode(leftNode);
}
return;
}
if (this.updateLength) {
let currentNode = leftNode;
while (currentNode && currentNode.next !== rightNode) {
this.length--;
currentNode = currentNode.next;
}
}
let leftGroupNodes = leftNode.group.nodes;
let rightGroupNodes = rightNode.group.nodes;
let layerCount = this.head.nodes.length;
for (let layerIndex = 0; layerIndex < layerCount; layerIndex++) {
let layerLeftNode = leftGroupNodes[layerIndex];
let layerRightNode = rightGroupNodes[layerIndex];
if (layerLeftNode && layerRightNode) {
layerLeftNode.next = layerRightNode;
layerRightNode.prev = layerLeftNode;
continue;
}
if (layerLeftNode) {
let layerRightmostNode = rightSearchPath[layerIndex];
if (!layerRightmostNode.group.isTail) {
layerRightmostNode = layerRightmostNode.next;
}
layerLeftNode.next = layerRightmostNode;
layerRightmostNode.prev = layerLeftNode;
continue;
}
if (layerRightNode) {
let layerLeftmostNode = leftSearchPath[layerIndex];
layerLeftmostNode.next = layerRightNode;
layerRightNode.prev = layerLeftmostNode;
continue;
}
// If neither left nor right nodes are present on the layer, connect based
// on search path to remove in-between entries.
let layerRightmostNode = rightSearchPath[layerIndex];
if (!layerRightmostNode.group.isTail) {
layerRightmostNode = layerRightmostNode.next;
}
let layerLeftmostNode = leftSearchPath[layerIndex];
layerLeftmostNode.next = layerRightmostNode;
layerRightmostNode.prev = layerLeftmostNode;
}
if (deleteLeft && matchingLeftNode) {
this._extractNode(matchingLeftNode);
}
if (deleteRight && matchingRightNode) {
this._extractNode(matchingRightNode);
}
}
_createEntriesIteratorAsc(currentNode) {
let i = 0;
return {
next: function () {
let currentGroup = currentNode.group;
if (currentGroup.isTail) {
return {
value: [currentNode.key, currentNode.value, i],
done: true
}
}
currentNode = currentNode.next;
return {
value: [currentGroup.key, currentGroup.value, i++],
done: currentGroup.isTail
};
},
[Symbol.iterator]: function () { return this; }
};
}
_createEntriesIteratorDesc(currentNode) {
let i = 0;
return {
next: function () {
let currentGroup = currentNode.group;
if (currentGroup.isHead) {
return {
value: [currentNode.key, currentNode.value, i],
done: true
}
}
currentNode = currentNode.prev;
return {
value: [currentGroup.key, currentGroup.value, i++],
done: currentGroup.isHead
};
},
[Symbol.iterator]: function () { return this; }
};
}
}
Where I am coming from is wondering how the theoretical concepts of a SkipList are applied in this implementation. But that is not my question, my question is very specific as described at the bottom. But take a look at a few things first. For example, the clear function is implemented like this, creating a fresh new SkipList:
clear() {
let headNode = {
prev: null
};
let tailNode = {
next: null
};
this.head = {
isHead: true,
key: undefined,
value: undefined,
nodes: [headNode]
};
this.tail = {
isTail: true,
key: undefined,
value: undefined,
nodes: [tailNode]
};
headNode.next = tailNode;
tailNode.prev = headNode;
headNode.group = this.head;
tailNode.group = this.tail;
this.length = this.updateLength ? 0 : undefined;
}
Notice the "group" as in headNode.group = this.head has this structure:
{
isTail: boolean,
isHead: boolean.
key: undefined,
value: undefined,
nodes: [node]
}
Or inserting a new node:
// Insert the entry.
let newNode = {
prev: prevNode,
next: prevNode.next
};
let newGroup = {
key,
value,
nodes: [newNode]
};
newNode.group = newGroup;
prevNode.next = newNode;
newNode.next.prev = newNode;
So a node has a prev/next/group set of properties, while a group has a key/value/nodes. Finally, notice that this.head.nodes[layerIndex] and such is being called in several places, which to me seems like it simply bypasses the linked-list nature of the SkipList and has an internal array that stores all the nodes anyway. Basically I am confused, I have been looking at this code for a day and it looks to me like each level node is storing an array of all the children nodes for that level, or something like that. I am confused with what is happening here.
Can one explain what the general model is for this SkipList implementation? That is:
The meaning of group and node objects, and why they have their structure they do.
What the nodes array is doing on a group.
For context, I am trying to learn how to implement a SkipList, after reading several papers on it and understanding the general idea. I want to put some constraints on it, so don't want to use any existing implementation. I want it to work in JavaScript and C, so it needs to be single-threaded in one context and multithreaded in the other, so I have a bit to learn. But knowing how to implement it in JavaScript, like what the above code is doing, is a good first step.
The summary of your question is:
The meaning of group and node objects, and why they have their structure they do.
What the nodes array is doing on a group.
I'll use an image taken from brilliant.org:
The linked lists appear as horizontal bands, while the arrays appear as vertical stacks.
A group corresponds to one distinct key in the set -- marked in yellow. The corresponding value is not depicted in the image, but is just payload and is not essential for understanding the data structure. A group has a nodes array -- displayed as a stack on top of the key. Each of these nodes belong to a different linked list in which they represent that same key. Those nodes have backreferences again to the group, so a node knows which key it represents, and which other linked lists have a node for this same key.
So the nodes array actually duplicates a key to different lists. One nodes array does not represent different keys in the collection -- just one. It is like an elevator to jump from one train (linked list) to another, with the same key.
The sizes of these arrays are randomly determined, but are intended to be small on average. This is driven by the stackUpProbability. It represents the probability that an array is extended with one more node (upwards in the image). The probability for having at least one node (for a key) is obviously 1, but the probability that the array will have at least one more node in it (and thus have a size of at least 2), is stackUpProbability. The probability that it will have size 3 is (stackUpProbability)²... etc.
This way, the bottom linked list (at layerIndex 1) will have all keys in the collection (one node per key, in sorted order), but any next layer will have fewer keys, and the top layer might only have a hand full. The lists with fewer nodes provide a way to travel fast along the collection in search of a value. This provides a proximity of where a value could be found. Via the "elevator" a search algorithm can step down to lower (slower) linked lists and continue the search there, until it reaches the lowest level where the key will be found (if present), or where it would have been (if not).
I'm working on a problem where given an array of file paths I would like to print the file structure. For example with the given array ["/a/b/c", "a/a/a", "/a/b/d"], the ideal structure would look like :
a
b
c
d
a
a
But my structure ends up looking more like this:
a
b
c
a
a
a
b
From what I can gather this is being caused by my tree not recognizing when a node already exists in a tree. So it is adding the node "a" three times as opposed to recognizing that an "a" already exists and traversing into it.
let paths = ["/a/b/c", "a/a/a", "/a/b/d"]
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(element) {
this.children.push(element)
}
}
const head = new TreeNode('Head');
let cur = head;
paths.forEach(element => {
cur = head;
let filePath = element.split('/');
filePath.shift();
filePath.forEach(element => {
let newNode = new TreeNode(element);
if(!cur.children.includes(newNode)) {
cur.addChild(newNode);
cur = cur.children[cur.children.length - 1];
} else {
cur = cur.children.indexOf(newNode);
}
})
})
var spaceAppend = function(num) {
let i = 0;
let space = "";
while(i < num) {
space += " ";
i++;
}
return space;
}
var traverse = function(node, level = 0){
if(node === null)
return;
console.log(spaceAppend(level), node.value)
if(node.children) {
for(const n of node.children) {
traverse(n, level + 1);
}
}
}
traverse(head)
Is there an issue with my tree implementation?
Some issues:
.includes() is not the right way to find a matching value. Use .find() instead.
.indexOf() will return an index, so that is not the right value you want to assign to cur in the else block.
shift may throw away an essential part of the path when it does not start with /. You can ease the processing by using .match() instead of .split(), so that you get exactly the non-empty parts of the path.
Less of an issue:
There is no need to define cur outside of the outer loop.
JavaScript has a native function for something like spaceAppend. You can use .repeat().
new TreeNode(element) is also called when you actually don't need it. Only create a new node when you know there is no matching node.
You could replace the inner .forEach() loop with .reduce(), which gives a better scope-handling for the cur variable.
Here is your code with those remarks taken into account:
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(element) {
this.children.push(element);
}
}
let paths = ["/a/b/c", "a/a/a", "/a/b/d"];
const head = new TreeNode('Head');
paths.forEach(element => {
// Use .match() to only get non-empty elements
let filePath = element.match(/[^\/]+/g);
filePath.reduce((cur, element) => {
// Use .find() instead of .includes()
let node = cur.children.find(child => child.value === element);
// Only create the node when needed:
if (!node) {
node = new TreeNode(element);
cur.addChild(node);
}
// Walk down one step in the tree
return node; // ...becomes the value of `cur`
}, head); // Initial value of reduction
});
const traverse = function(node, level=0) {
if (node === null) return;
// Use .repeat():
console.log(" ".repeat(level), node.value);
if (node.children) {
for (const n of node.children) {
traverse(n, level + 1);
}
}
}
traverse(head);
Is the starter array meant to be ["/a/b/c", "/a/a/a", "/a/b/d"] ("/a/a/a" instead of ("a/a/a")?
I think the crux of the problem you're having is the line
if(!cur.children.includes(newNode)) { ... }
When a new node is created, even if it has the same value as a previous one, it will not result in equity when comparing the two TreeNode objects. You need to compare the value of the nodes, not the nodes themselves.
So an example with a simplified version of your node object:
class TreeNode {
constructor(value) {
this.value = value;
}
}
a1 = new TreeNode('a');
a2 = new TreeNode('a');
console.log("a1 == a2");
console.log(a1 == a2); // false
console.log("a1.value == a2.value");
console.log(a1.value == a2.value); // true
I adjusted the inner forEach loop with one that compares the values instead of the TreeNode objects
filePath.forEach(element => {
let newNode = new TreeNode(element);
let tempNode = null;
for (var i = 0; i < cur.children.length; i++) {
if (cur.children[i].value == newNode.value) {
tempNode = cur.children[i];
}
}
if (tempNode == null) {
cur.addChild(newNode);
cur = newNode;
} else {
cur = tempNode;
}
});
Full code snippet on codepen
Object equality in javascript isn't particularly nice to deal with see this other answer for more information
Here is a solution using lodash and object-treeify. While it's simpler code, there is obviously a trade-off introducing additional dependencies.
This solution works by first converting the paths into a tree structure and then visualizing it using object-treeify
// const lodash = require('lodash');
// const objectTreeify = require('object-treeify');
const myPaths = ['/a/b/c', 'a/a/a', '/a/b/d'];
const treeify = (paths) => objectTreeify(paths.reduce((p, c) => {
lodash.set(p, c.match(/[^/]+/g));
return p;
}, {}), {
spacerNoNeighbour: ' ',
spacerNeighbour: ' ',
keyNoNeighbour: '',
keyNeighbour: ''
});
console.log(treeify(myPaths));
/* =>
a
b
c
d
a
a
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/lodash#4.17.20"></script>
<script src="https://bundle.run/object-treeify#1.1.31"></script>
Disclaimer: I'm the author of object-treeify
class LinkedListNode {
constructor(value) {
this.value = value;
this.next = null;
}
}
let head = new LinkedListNode("head");
let x = [1, 1, 1, 1, 4, 10, 10, 3, 10, 9, 5, 5, 5, 8];
for (let ele of x) {
let y = new LinkedListNode(ele);
let pointer = head;
while (pointer.next != null) {
pointer = pointer.next;
}
pointer.next = y;
}
Can someone explain why the following 'solution' leads to an infinite loop?
let removeDup = function(sll) {
let array = []
let pointer = sll;
while (pointer) {
if (array.includes(pointer.value)){
} else {
array.push(pointer.value);
sll.next = pointer;
sll = sll.next;
}
pointer = pointer.next;
}
}
It appears that if I
let pointer = sll.next;
or
let array = [sll.value]
then it works fine but if I run it as is then it leads to an infinite loop. I can see why it may make a linked list with 2 duplicates of the first value but I can't understand why it makes an infinite loop. Alternatively, if anyone can point me in the right direction for debugging this then that would be appreciated also!
Looks like you end up defining a node that references itself within your else condition.
You may be looking for something like this:
class LinkedListNode {
constructor(value) {
this.value = value;
this.next = null;
}
}
let head = new LinkedListNode("head");
let x = [1, 1, 1, 1, 4, 10, 10, 3, 10, 9, 5, 5, 5, 8];
for (let ele of x) {
let y = new LinkedListNode(ele);
let pointer = head;
while (pointer.next != null) {
pointer = pointer.next;
}
pointer.next = y;
}
function removeDup(currentNode = sll) {
const seen = {};
let lastUnique;
while (currentNode) {
if (seen[currentNode.value]) {
lastUnique.next = currentNode.next;
} else {
seen[currentNode.value] = true;
lastUnique = currentNode;
}
currentNode = currentNode.next;
}
}
removeDup(head);
let outputNode = head;
while (outputNode) {
outputNode = outputNode.next;
if (outputNode) {
console.log(outputNode.value);
}
}
How to investigate your bug
You can use a debugger, but for primitive people like me, you can also use console.log
In case of infinite loop, what you want is to break free
let count = 0
while (pointer && count++<5) {
//whatever
}
Even if you fail in your algorithm you will still exit
Output your variable
Be creative and spam console.log wherever you see fit. put some string to remind you what junk you outputed
while (pointer) {
if (array.includes(pointer.value)){
console.log('cached skip')
} else {
console.log('pointervalue', pointer.value)
array.push(pointer.value);
sll.next = pointer;
sll = sll.next;
console.log('sllendloop', sll)
}
pointer = pointer.next;
}
Fixing your code
Note: do not use array for cache because it is O(n) look
You may use a Set (O(1)) instead
const removeDup = function(sll) {
const dupes = new Set()
let cur = { next: null }
// you can also as you suggested initialize cur as sll
// in which case you must mark it as "consumed" idem add the value to dupes
let sllIter = sll
while (sllIter) {
if (dupes.has(sllIter.value)) {
// early continue to avoid nesting else
// subjective preference
sllIter = sllIter.next
continue
}
dupes.add(sllIter.value)
// link our node to the currently being iterated node
cur.next = sllIter;
// advance our node
cur = sllIter
// advance iter
sllIter = sllIter.next
}
return sll
}
const l = (value, next) => ({ value, next })
const sllStr = ({ value: a, next: b }) => b ? `${a} -> ${sllStr(b)}`: a
const sll = l(1, l(1, l(2, l(1, l(2, l(3))))))
console.log(sllStr(removeDup(sll))) // 1 -> 2 -> 3
You may write a micro Linked List library perhaps. Here is one with function descriptions;
toList : takes an array converts it into a list
foldList : Something like reduce. Takes a list, a function and an accumulator.
sum : takes a list and gives it's sum :)
prt : pretty print a list
nub : remove dupes from a list.
function List(e){
this.data = e;
this.next = null;
}
List.fromArray = function([a,...as]){
var h = new List(a),
t = as.reduce((l,e) => [l[0],l[1].next = new List(e)], [h,h]);
return t[0];
};
List.prototype.fold = function (f,a){
var newa = f(a, this.data);
return this.next ? this.next.fold(f, newa)
: newa
};
List.prototype.log = function(){
return this.fold((a,e) => a + e + " -> ", "") + "null";
};
List.prototype.nub = function(){
var o = this.fold((a,e) => (a[e] = e, a), {}),
a = Object.values(o);
return List.fromArray(a);
}
var arr = [1, 1, 1, 1, 4, 10, 10, 3, 10, 9, 5, 5, 5, 8],
lst = List.fromArray(arr),
sum = l => l.fold((a,e) => a + e, 0), // just for fun
res = lst.nub().log();
console.log(res);
nub is O(n).
I have following tree lets call it Tree One.
1
/ \
/ \
2 3
/
/
4
Now I want to replace the root node with 2. Then above tree will become something like this. Lets call it Tree Two
2
/ \
/ \
4 1
\
\
3
How can I implement above for arrays input as described above?
UPDATE
I already tried with Linkedlist. Which is below however, it is not working for above inputs(i.e. array).
function replaceRootNode(tree, x) {
var y = x.left;
x.left = y.right;
if (y.right) {
y.right.parent = x;
}
y.parent = x.parent;
if (!x.parent) {
tree.root = y;
} else {
if (x === x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
}
y.right = x;
x.parent = y;
}
UPDATE 2
My above solution using likedlist is based on Splay Tree. But I need it for arrays input.
Your definition was not complete so I added the following definitions:
New selected root does not have to be direct child of the current root (i.e. you can go in your example directly from state 1 to state 3).
If the new selected root had both left and right children, one of them is assigned to its previous parent.
What I did is to first transform it to a structured json and do the manipulation there. I used lodash to make code cleaner.
Note that my code has functions to convert from your format to json and back. When you look at the code, make sure you open the console, also, you have a few samples of more complex 'trees' commented out, you can try them as well.
// var tree = [1, [2, [4], [5]], [3]];
var tree = [1, [2, [4]],
[3]
];
// var tree = [1, [2, [4, [5, [6,[7]]]]], [3]];
var zipTree = _.partial(_.zipObject, ['root', 'left', 'right']);
var toTree = _.flow(zipTree, _.partial(_.pick, _, _.identity));
function toTreeRecurse(data) {
var result = toTree(data);
if (_.isArray(result.left)) {
result.left = toTreeRecurse(result.left);
}
if (_.isArray(result.right)) {
result.right = toTreeRecurse(result.right);
}
return (result);
}
function toArrayRecurse(tree) {
var result = [tree.root];
if (tree.left) {
result.push(toArrayRecurse(tree.left));
}
if (tree.right) {
result.push(toArrayRecurse(tree.right));
}
return (result);
}
function findValueParent(tree, value) {
if (!tree) {
return (null);
}
var result;
if (_.get(tree, 'left.root') === value) {
return (tree);
} else if (_.get(tree, 'right.root') === value) {
return (tree);
} else if (result = findValueParent(tree.left, value)) {
return (result);
} else if (result = findValueParent(tree.right, value)) {
return (result);
} else {
return (null);
}
}
function setRoot(tree, value) {
var rootParent = findValueParent(tree, value);
var newRoot;
if (rootParent) {
var fromSide, toSide;
if (findValueParent(tree.left, value) || (_.get(tree, 'left.root') === value)) {
fromSide = 'left';
toSide = 'right';
} else if (findValueParent(tree.right, value) || (_.get(tree, 'right.root') === value)) {
fromSide = 'right';
toSide = 'left';
}
newRoot = _.get(rootParent, fromSide);
if (_.get(newRoot, toSide)) {
_.set(rootParent, fromSide, _.get(newRoot, toSide));
} else {
delete rootParent[fromSide];
}
_.set(newRoot, toSide, tree);
return (newRoot);
} else {
console.log('value does not exist');
return (null);
}
}
console.log('original: ', JSON.stringify(tree));
console.log('original as json tree: ', JSON.stringify(toTreeRecurse(tree)));
console.log('setting root to 2: ', JSON.stringify(toArrayRecurse(setRoot(toTreeRecurse(tree), 2))));
console.log('setting root to 4: ', JSON.stringify(toArrayRecurse(setRoot(toTreeRecurse(tree), 4))));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<div>Demo, make sure you open the devtools to see the console print</div>
I don't believe there is a way to do this exclusively with arrays. Linked lists are definetly better suited for your issue.
So if you can only accept arrays as an input, you could do the following:
Convert your input array into a linked list.
Use the replaceRootNode function you provided to replace the node.
Convert linked list back to array.
Here is a code example I made to convert the array:
var arr = [2, [1, [3]], [4]];
var tree = {
root: null
};
arrayToList(arr, tree);
console.log(tree);
function arrayToList(array, tree){
var node = {
data: array[0],
left: null,
right: null
};
if (tree.root == null) {
tree.root = node;
}
if(array[1] instanceof Array) {
node.left = arrayToList(array[1], tree);
} else {
node.left = array[1];
}
if(array[2] instanceof Array) {
node.right = arrayToList(array[2], tree);
} else {
node.right = array[2];
}
return node;
}
Fiddle (output in console).
I'm using a Postorder traversing of the tree, which is Left > Right > Root. So it will output your trees mirrored compared to your examples above.
If you prefer to visit the right node first, you can do this simple edit: Fiddle. Though you typically always visit Left before Right.
Anyway, then you can replace your root node with your function (haven't tried it).
Then, you can do a similar function to convert the Re-arranged tree back into an array.