Unable to find all modes in a tree - javascript

I'm trying to solve the following algorithms problem:
Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred element) in the given BST.
Assume a BST is defined as follows:
The left subtree of a node contains only nodes with keys less than or equal to the node's key.
The right subtree of a node contains only nodes with keys greater than or equal to the node's key.
Both the left and right subtrees must also be binary search trees.
For example:
Given BST [1,null,2,2],
1
\
2
/
2
return [2].
Note: If a tree has more than one mode, you can return them in any order.
Follow up: Could you do that without using any extra space? (Assume that the implicit stack space incurred due to recursion does not count).
I have written the following code, but the last test case does not pass:
class TreeNode {
constructor(val, left, right) {
this.val = (val === undefined ? 0 : val)
this.left = (left === undefined ? null : left)
this.right = (right === undefined ? null : right)
}
}
//updated code, doesn't seem to work, not sure if I am editing it the way it is suggested.
const findMode = root => {
if (!root) return []
if (root && !root.left && !root.right) return [root.val]
const hash = {}
let current = root
let result = []
let keys
const dfs = c => {
if (!c) return
if (c.left) dfs(c.left)
hash[c.val] = (hash[c.val] || 0) + 1
if (c.right) dfs(c.right)
}
dfs(current)
// keys = Object.keys(hash)
// if (keys.length <= 1) return [+keys]
// else keys.reduce((a, b) => {
// if (hash[a] === hash[b]) result.push(+a, +b)
// else if (hash[a] > hash[b]) {
// result.push(+a)
// } else result.push(+b)
// })
// return result
keys = Object.keys(hash);
keys.sort((a, b) => hash[b] - hash[a]);
keys.forEach(key => {
if (hash[key] === keys[0]) result.push(key);
})
return result
}
Here are the test cases:
const tree = new TreeNode(1, null, new TreeNode(2, new TreeNode(2)))
console.log(findMode(tree)) //[2]
const tree2 = new TreeNode(1, null, new TreeNode(2))
console.log(findMode(tree2)) //[1,2]
const tree3 = new TreeNode(2147483647)
console.log(findMode(tree3)) //[2147483647]
const tree4 = new TreeNode(1, new TreeNode(1))
console.log(findMode(tree4)) // should be [1], but is []
What am I doing wrong?

In case of const tree4 = new TreeNode(1, new TreeNode(1)) your hash has only one key and reduce doesn't make sense on a single element array. See this.
In the case of single element array , you may do something like below:
if ( Object.keys(hash).length <= 1 ) return Object.keys(hash)
I don't think reduce is the right thing to do here. You need to sort the keys by their values in decreasing order and take the highest keys as below:
var keys = Object.keys(hash);
keys.sort((a,b) => hash[b]-hash[a]);
keys.forEach( key => {
if ( hash[key] === hash[keys[0]] ) result.push(key);
})
return result

Related

insert() function doesn't add nodes to the binary search tree

I'm trying to make a binary search tree. If I start with an array my function makes a binary search tree out of that array (everything fine here). like for an array like let a = [2,4,5,3,9,7,3,8,5]; the tree looks like the picture. my problem is with the insert() function. If I start with an empty array and add a node to it, it works. However, when I add a second node, that second node won't be added to my tree, and my tree is shown as having only 1 node in it (the root node). Here the snippet:
const Node = (data, left = null, right = null) => {
return {data, left, right};
};
const Tree = array => {
const remDupsAndSort = array => {
const mergeSort = array => {
if(array.length <= 1) return array;
let leftArr = array.slice(0, array.length / 2);
let rightArr = array.slice(array.length / 2);
return merge(mergeSort(rightArr), mergeSort(leftArr))
};
const merge = (leftArr, rightArr) => {
let sorted = [];
while(leftArr.length && rightArr.length){
if(leftArr[0] < rightArr[0]){
sorted.push(leftArr.shift());
}else{
sorted.push(rightArr.shift());
}
};
return [...sorted, ...leftArr, ...rightArr]
};
return mergeSort([... new Set(array)])
};
array = remDupsAndSort(array);
const buildTree = (array, start, end) => {
if(start > end) return null;
let mid = Math.floor((start + end) / 2);
let node = Node(array[mid]);
node.left = buildTree(array, start, mid - 1);
node.right = buildTree(array, mid + 1, end);
return node;
};
const insert = value => {
if(!root) return root = Node(value);
current = root;
while(current){
if(value < current){
current = current.left
}else{
current = current.right
}
}
current = Node(value)
// if(!current){
// current = Node(value)
// // }else{
// if(value < current){
// current.left = insert(value, current.left)
// }else{
// current.right = insert(value, current.right)
// }
// }
return root
};
const prettyPrint = (node = root, prefix = '', isLeft = true) => {
if(node){
if (node.right !== null) {
prettyPrint(node.right, `${prefix}${isLeft ? '│ ' : ' '}`, false);
}
console.log(`${prefix}${isLeft ? '└── ' : '┌── '}${node.data}`);
if (node.left !== null) {
prettyPrint(node.left, `${prefix}${isLeft ? ' ' : '│ '}`, true);
}
}else{
console.log(node)
}
}
let root = buildTree(array, 0, array.length - 1);
return {root, prettyPrint, insert}
};
let a = [2,4,5,3,9,7,3,8,5];
let b = [];
let c = [1,2,4,5,6,7]
let f = Tree(a)
let d = Tree(b)
let e = Tree(c)
d.insert(4)
// d.insert(8) ---> doesn't work
// d.prettyPrint()
// f.insert(1) ---> doesn't work
f.prettyPrint()
// e.prettyPrint()
// console.log(d.root)
if I run d.prettyPrint() I'll get └── 4 just as expected. But if I run d.insert(8) after that 8 isn't added to the tree and the code returns └── 4 again. To make matters more confusing if I console.log(d.root) it returns null even though my prettyPrint function returns └── 4 as the root.
Clearly I expect the nodes be added to the tree. On one of my attempts I tried to write the code like this:
const insert = (value, current = root) => {
if(!current){
current = Node(value)
}else{
if(value < current){
current.left = insert(value, current.left)
}else{
current.right = insert(value, current.right)
}
}
return current
};
even though I assigned current = root the code returned null for d.insert(4)
There are these issues in your insert function:
current is implicitly defined as a global variable -- this wouldn't parse in strict mode. It should be declared as a local variable, using let
The value is compared with a node object instead of with the data of that node. So value < current should be changed to value < current.data
The assignment of the new node object to current -- after the loop -- will not mutate the tree. It merely assigns that object to that variable. Such assignment can never change the tree, just like current = current.right does not mutate the tree either. You need instead to assign the new node to either the left or right property of the parent of current. So this assignment should happen one step earlier.
Correction:
const insert = value => {
if(!root) return root = Node(value);
let current = root; // Define with `let`
while(current){
if(value < current.data){ // Compare with the data, not the node
// Mutate the parent node when inserting
if (!current.left) return current.left = Node(value);
current = current.left
}else{
if (!current.right) return current.right = Node(value);
current = current.right
}
}
};

Node js path binary tree

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()}`] : []
}
}

Print a hierarchical tree structure in javascript

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

How to understand scope/closure with recursion and helper function?

Here are examples with simple questions:
Example 1: Find maximum depth of binary tree.
I got the right answer but don't know why my original wrong answer is wrong.
Right answer:
var maxDepth = function(root) {
if (root === null) return 0;
var maxDepth = 1;
maxDepth = maxDepthHelper(root, 1, maxDepth);
return maxDepth;
};
function maxDepthHelper(tree, depth, maxDepth) {
if (tree.left === null && tree.right === null) {
maxDepth = depth > maxDepth ? depth : maxDepth;
return maxDepth;
}
if (tree.left) {
maxDepth = maxDepthHelper(tree.left, depth + 1, maxDepth);
}
if (tree.right) {
maxDepth = maxDepthHelper(tree.right, depth + 1, maxDepth);
}
return maxDepth;
}
Wrong answer:
var maxDepth = function(root) {
if (root === null) return 0;
var maxDepth = 1;
maxDepthHelper(root, 1, maxDepth);
return maxDepth;
};
function maxDepthHelper(tree, depth, maxDepth) {
if (tree.left === null && tree.right === null) {
maxDepth = depth > maxDepth ? depth : maxDepth;
return;
}
if (tree.left) {
maxDepthHelper(tree.left, depth + 1, maxDepth);
}
if (tree.right) {
maxDepthHelper(tree.right, depth + 1, maxDepth);
}
}
It has something to do with me thinking the maxDepth should be changed by the helper function and ultimately when I return that it should return changed but it doesn't. It just returns 1 which is the original thing I assign it. But here in the example below, I am able to change a variable from the parent in the helper function, so what am I missing here?
Example 2: Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.
Solution:
var kthSmallest = function(root, k) {
let smallestArr = [];
kthSmallestHelper(root, k, smallestArr);
return smallestArr.pop()
};
function kthSmallestHelper(bst, k, array) {
if (bst === null) return;
kthSmallestHelper(bst.left, k, array);
if (array.length === k) return;
array.push(bst.val);
kthSmallestHelper(bst.right, k, array);
}
The variable maxDepth (lets call this the outer maxDepth) in the function maxDepth (unfortunate naming) stores a value (the number 1). When you call maxDepthHelper(root, 1, maxDepth), the value 1 is passed in and stored in the local variable maxDepth inside maxDepthHelper. We can assign anything to the local maxDepth, but it won't affect the value stored in the outer maxDepth, because they are two different variables.
The variable smallestArr in the function kthSmallest stores a value; that value is a pointer (the memory location) to an empty array. When kthSmallestHelper(root, k, smallestArr) is called, just like before, that value (the pointer) is passed in and stored inside the local variable array in kthSmallestHelper. Effectively, now array and smallestArr both store a pointer (the memory location) to the same empty array. If we now do any assignment to array, like array=['some new arr'], the variable smallestArr won't get affected. But when you call a mutation method, like array.push(bst), what happens is the Javascript engine looks at the pointer stored in array, and modifies the array stored at that memory location. Because smallestArr stores the pointer to this modified array, if you call smallestArr.pop(), the Javascript engine will pop the last item of the modified array.
The important thing to remember is anytime you write an expression like let x = /* some array or object */, an array/object is created, then a pointer to that array/object is stored in the variable. If you write let x = /* some primitive value (like 3)*/, the value 3 is directly stored in the variable.
In the second program, maxDepth is a number and passed by value (copy), not by reference. The recursive calls are effectively no-ops, and their return values are immediately discarded. This is a common mistake for beginners that are learning how different variable types are passed from one function to another.
That said, recursion is a functional heritage and so using it with functional style yields the best results. This means avoiding side effects like mutation and variable reassignment. You can simplify your depth program a lot -
function depth(tree)
{ if (tree == null)
return 0
else
return 1 + max(depth(tree.left), depth(tree.right))
}
function max (a, b)
{ if (a > b)
return a
else
return b
}
An expression-based syntax is often preferred because expressions evaluate to values, whereas statements (like if and return) do not -
const depth = tree =>
tree == null
? 0
: 1 + max(depth(tree.left), depth(tree.right))
const max = (a, b) =>
a > b
? a
: b
Your kthSmallest program is more difficult but JavaScript's imperative-style generators make quick work of the problem. Mutation k-- is used but cannot be observed from outside of the function -
function *inorder (tree)
{ if (tree == null) return
yield* inorder(tree.left)
yield tree.val
yield* inorder(tree.right)
}
function kthSmallest (tree, k)
{ for (const v of inorder(tree))
if (k-- == 0)
return v
}
The pure expression form of this program is slightly different -
const inorder = tree =>
tree == null
? []
: [ ...inorder(tree.left), tree.val, ...inorder(tree.right) ]
const kthSmallest = (tree, k) =>
inorder(tree)[k]
Here's a functioning demonstration -
import { depth, fromArray, inorder, kthSmallest } from "./Tree"
const rand = _ =>
Math.random() * 100 >> 0
const t =
fromArray(Array.from(Array(10), rand))
console.log("inorder:", Array.from(inorder(t)))
console.log("depth:", depth(t))
console.log("0th:", kthSmallest(t, 0))
console.log("1st:", kthSmallest(t, 1))
console.log("2nd:", kthSmallest(t, 2))
console.log("99th:", kthSmallest(t, 99))
Output -
inorder: [ 12, 14, 25, 44, 47, 53, 67, 70, 85, 91 ]
depth: 5
0th: 12
1st: 14
2nd: 25
99th: undefined
Writing modules like Tree below is a good practice for separating concerns and organising your code -
// Tree.js
const empty =
null
const node = (val, left = empty, right = empty) =>
({ val, left, right })
const fromArray = (a = []) =>
a.length < 1
? empty
: insert(fromArray(a.slice(1)), a[0])
const insert = (t = empty, v = null) =>
t === empty
? node(v)
: v < t.val
? node(t.val, insert(t.left, v), t.right)
: v > t.val
? node(t.val, t.left, insert(t.right, v))
: t
const depth = (tree = empty) => ...
const inorder = (tree = empty) => ...
const kthSmallest = (tree = empty, k = 0) => ...
export { depth, empty, fromArray, inorder, kthSmallest, node }
Expand the snippet below to verify the results in your own browser -
const empty =
null
const node = (val, left = empty, right = empty) =>
({ val, left, right })
const fromArray = (a = []) =>
a.length < 1
? empty
: insert(fromArray(a.slice(1)), a[0])
const insert = (t = empty, v) =>
t === empty
? node(v)
: v < t.val
? node(t.val, insert(t.left, v), t.right)
: v > t.val
? node(t.val, t.left, insert(t.right, v))
: t
const inorder = (tree = empty) =>
tree === empty
? []
: [ ...inorder(tree.left), tree.val, ...inorder(tree.right) ]
const kthSmallest = (tree = empty, k = 0) =>
inorder(tree)[k]
const depth = (tree = empty) =>
tree == null
? 0
: 1 + Math.max(depth(tree.left), depth(tree.right))
const rand = _ =>
Math.random() * 100 >> 0
const t =
fromArray(Array.from(Array(10), rand))
console.log("inorder:", JSON.stringify(Array.from(inorder(t))))
console.log("depth:", depth(t))
console.log("0th:", kthSmallest(t, 0))
console.log("1st:", kthSmallest(t, 1))
console.log("2nd:", kthSmallest(t, 2))
console.log("99th:", kthSmallest(t, 99))

JavaScript to split data and calculate sums

I believe what I need are two JavaScript functions. I am receiving a comma separated string that holds two types of data: 1) device name followed by 2) numeric value. These two values are separated by a comma, and each set is also separated by a comma. Example string below:
Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7
What I want to do is create two separate functions. The first function finds the unique device names and returns just the names in a comma separated string. The second function would calculate the sum of the numeric values for each device. The expected results from the example string above would return:
Function 1 (Device List):
Device_A, Device_B, Device_C
Function 2 (Sums per Device List):
15,10,9
The lists do not need to return in any particular order as long at they both match up. All I have successfully done at this point is return a list of unique values (including numeric values)... I'm stuck on separating the list, but still referring to device name to sum up all of the values.
Thanks in advance. Let me know if you have any questions!
Matt
You could use an object for collecting the names and count.
This edit contains a shared function and two function for the result in equal order.
function getGrouped(data) {
var array = data.split(','),
temp = Object.create(null),
i = 0;
while (i < array.length) {
temp[array[i]] = (temp[array[i]] || 0) + +array[i + 1] || 0;
i += 2;
}
return temp;
}
function getDevices(data) {
var temp = getGrouped(data);
return Object.keys(temp).sort().join();
}
function getCounts(data) {
var temp = getGrouped(data);
return Object.keys(temp).sort().map(function (k) { return temp[k]; }).join();
}
var data = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7";
console.log(getDevices(data));
console.log(getCounts(data));
When starting out on a problem like this, I think it's wise to not worry about doing it in a single loop or in a fancy one-liner at first.
A) Start out by defining what data structures you need and how to go from one format to another:
Convert my string of data to a list of keys and values
Somehow group these keys and values based on the key
Sum the values for each group
Return a list of all unique keys
Return a list of all summed values
B) Then, try to see if any of the code you've written has the potential be re-used by other parts of your application and refactor accordingly.
C) Finally, assess if there are performance bottle necks and only if there are, optimize for performance.
A. A function for each step:
// 1. From string to array of keys and values
// You already figured this one out. Split by ","!
const namesAndValuesFromString =
str => str.split(",");
// 2. Grouping by key
// Let's first make pairs:
const deviceValuePairs = devicesAndValues => {
let pair = [];
const pairs = [];
devicesAndValues.forEach(x => {
pair.push(x);
if (pair.length === 2) {
pairs.push(pair);
pair = [];
}
});
return pairs;
};
// Key value pairs are a nice starting point for constructing a grouped object:
const kvpsToDeviceValuesObj = kvps => {
const valuesByDevice = {};
kvps.forEach(([key, value]) => {
value = Number(value);
if (!valuesByDevice[key]) {
valuesByDevice[key] = [];
}
valuesByDevice[key].push(value);
});
return valuesByDevice;
};
// 3. Now, we can get to summing the values arrays
const sumValueArrays = valuesByDevice => {
const summedValuesByDevice = {};
// Loop over the objects entries
Object.entries(valuesByDevice).forEach(
([key, values]) => {
summedValuesByDevice[key] = values
.reduce((a, b) => a + b);
}
);
return summedValuesByDevice;
};
// 4. + 5. Now that we have an object with device ids as keys, and summed values inside, we can retrieve the two lists
const getDevices = Object.keys;
const getSums = Object.values;
// Running the code:
const namesAndValues =
namesAndValuesFromString("A,5,C,2,A,10,B,8,B,2,C,7");
console.log(namesAndValues);
const kvps = deviceValuePairs(namesAndValues);
console.log(kvps);
const valuesByDevice = kvpsToDeviceValuesObj(kvps);
console.log(valuesByDevice);
const sumValues = sumValueArrays(valuesByDevice);
console.log(sumValues);
const devices = getDevices(sumValues);
console.log(devices);
const sums = getSums(sumValues);
console.log(sums);
B. Refactoring!
Once you understand each of those steps, you'll start to see things that can be generalized or combined. That's where the fun starts :)
// UTILITIES
const split = del => arr => arr.split(del);
const toPairs = arr => {
let pair = [];
return arr.reduce(
(pairs, x) => {
pair.push(x);
if (pair.length === 2) {
pairs.push(pair);
pair = [];
}
return pairs;
}, []);
};
const sum = (x, y = 0) => +x + y;
const kvpsToGroups = grouper => kvps =>
kvps.reduce(
(groups, [key, value]) => Object.assign(groups, {
[key]: grouper(value, groups[key])
}), {});
// YOUR APP
const sumGrouper = kvpsToGroups(sum);
const dataSplitter = split(",");
const parseData = str => sumGrouper(toPairs(dataSplitter(str)));
// MAIN
const result = parseData("A,5,C,2,A,10,B,8,B,2,C,7");
console.log("devices:", Object.keys(result));
console.log("sums:", Object.values(result));
another way by regexs
let str = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7", obj = {}
str.match(/(\w+,[0-9]+)/g).forEach((s) => {
s = s.split(',')
obj[s[0]] = (obj[s[0]] || 0) + (Number(s[1]) || 0)
})
console.log(obj)
Something like this should do it:
var input = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7";
var output = input.split(',').reduce((accumulator, currentValue, currentIndex, array) => {
accumulator[currentValue] = (accumulator[currentValue] || 0)
+ parseInt(array[currentIndex + 1]);
array.splice(0,1);
return accumulator;
}, {});
console.log(Object.keys(output));
console.log(Object.keys(output).map(k => output[k]));

Categories