JavaScript - How to reset a local variable during recursion? - javascript

There is an object with "value" or/and "children" as properties. The problem is to add all the values and return the sum. This is working. I have used recursion. I am using a global variable to store the sum.
Please refer - https://jsfiddle.net/ws6ty78b/1/
function sumup(node) {
sum+=node.value;
if (node.children && node.children.length > 0) {
for (var i =0; i < node.children.length; i++) {
sumup(node.children[i]);
}
}
return sum
}
The problem - While invoking the same function again, I am not getting same result. The sum is doubling. I DO KNOW why it is happening, that is since I am using a global variable.
Question - For the above problem, is there a way I get the same result for any number of invocations.
Please note the restrictions -
1) Do not change the function signature and declaration.
2) Use the 'sum' variable inside the 'sumup' function.
3) Do not use any extra variable.
4) Only make changes within the sumup function.

You could simply sum the values of each child to the value of the current node:
function sumup(node) {
sum=node.value;
if (node.children && node.children.length > 0) {
for (var i =0; i < node.children.length; i++) {
sum+=sumup(node.children[i]);
}
}
return sum
}

You could call sumup while recursing and assign/test against a custom this:
var obj = {
"value": 4,
"children": [{
"value": 2,
"children": [{
"value": 1
}]
},
{
"value": 9
}
]
};
var sum = 0;
function sumup(node) {
//Only make change within this function body
if (!this || !this.recurse) sum = 0;
sum += node.value;
if (node.children && node.children.length > 0) {
for (var i = 0; i < node.children.length; i++) {
sumup.call({ recurse: true }, node.children[i]);
}
}
return sum
}
console.log(sumup(obj));
console.log(sumup(obj));
Alternatively, you could do away with the global variable entirely and use a recursive reduce over the children instead:
var obj = {
"value": 4,
"children": [{
"value": 2,
"children": [{
"value": 1
}]
},
{
"value": 9
}
]
};
const sumup = (node) => (
node.value + (node.children
? node.children.reduce((a, child) => a + sumup(child), 0)
: 0
)
);
console.log(sumup(obj));
console.log(sumup(obj));

In order to accomplish your goal the function sumup() will be invoked N times. We don't know how much this N is, since it depends on the number of recursions for the children nodes. Also, thanks to the restrictions, we can't edit the function signature and cannot write code elsewhere to do pretty anything, not even manually set sum to 0.
Therefore we need a way to distinguish one bunch of invocations from the NEXT bunch, and reset it.
My idea is we set a threshold, use a closure. From the first invocation, in the example, you have 5 seconds of time to keep calling and afterwards it will start anew. This is really all we can do unless another way to distinguish the invocations is provided.
EDIT: What I was trying here is keep all the recursions as a base case and not altering the object. Since OP has accepted that solution, I am adding a node resetter property. If any node has resetter not defined or null or zero or undefined, this will reset the sum. We must assume it is not defined in the started object. As I said this is kind of a weak assumption. By defining it when recursing, current sum is carried over.
I will also keep the original time threshold idea for eventual interest.
var obj = {
"value": 4,
"children": [
{
"value": 2,
"children": [
{
"value": 1
}
]
},
{
"value": 9
}
]
}
const sumup = (function(){
var lastTime = 0;
var newTime = 0;
var sum = 0;
const timeThreshold = 5000; // 5 seconds
return function(node) {
newTime = new Date().getTime();
if(!node["resetter"] || (newTime-lastTime >= timeThreshold)){
sum=0;
lastTime = newTime;
}
sum+=node.value;
if (node.children && node.children.length > 0) {
for (var i =0; i < node.children.length; i++) {
sumup(Object.assign({"resetter":true},node.children[i]));
}
}
return sum;
}
})();
console.log(sumup(obj)); //16
console.log(sumup(obj)); //32! should print 16 everytime

Related

How to append a property to another property of type object in JavaScript

I am trying to figure out how to append a new value to the same property of an object. I have this code below:
let xp_logs = {};
for (let i = 0; i <= rows.length; i++) {
if (rows[i]) {
if(member.roles.cache.find(r => r.id == roles.admin)) {
xp_logs[todaysDate] = {}
xp_logs[todaysDate][member[i].displayName] = {id: member[i].user.id, xp: rows[i].xp}
console.log(member.displayName) // returns multiple names, last name is Henry (from rows)
}
}
}
fs.writeFileSync("./xp_logs.json", "\n" + JSON.stringify(xp_logs, null, 2));
The value [member.displayName] changes every for loop, and I want every loop to create a new value within [todaysDate] with [member.displayName] property name.
It currently replaces property names with the next name from the row, instead of adding a new property, and at the end of the for loop, it only saves the last name from the row.
{
"7/21/2022": {
"Henry": {
"id": "331231456712356126",
"xp": 280
}
}
}
However, I want to make it look like this:
{
"7/21/2022": {
"Sierra": {
"id": "123561241241241244",
"xp": 190
},
"Cammy": {
"id": "556574574574234234",
"xp": 600
},
"Henry": {
"id": "331231456712356126",
"xp": 280
}
}
}
You're resetting xp_logs[todaysDate] to an empty object each time through the loop. You should only do that if the property doesn't already exist.
Also, for loops should use the condition i < rows.length;, not i <= rows.length;. Array indexes go from 0 to length-1. With this fix you don't need the extra check if (rows[i]), which was only needed to skip the iteration after the end of the array.
let xp_logs = {};
for (let i = 0; i < rows.length; i++) {
if (member.roles.cache.find(r => r.id == roles.admin)) {
if (!xp_logs[todaysDate]) {
xp_logs[todaysDate] = {};
}
xp_logs[todaysDate][member.displayName[i]] = {
id: member.user.id,
xp: rows[i].xp
}
console.log(member.displayName) // returns multiple names, last name is Henry (from rows)
}
}

I keep getting cannot read property error

I am trying to do a bubblesort. I am using this algorithm to sort a 2d array and I keep getting an error. Here is the function:
var array = [
[ "Rober Hill" , 123.54 ],
[ "Chrsitopher Reddkin", 54.67 ],
[ "Maggie Woods" , 1000.87 ],
[ "Jennifer Jones" , 3.34 ],
[ "Marcus Parker" , 64.98 ]
];
table = document.getElementById("table");
function bubbleSort(array, length, element)
{
var swapped = false;
do
{
for (var a = 0; a < 5; a++) // Line 59
{
if (array[a][1] > array[a+1][1])
{
var temp = array[a][1];
array[a][1] = array[a+1][1];
array[a+1][1] = temp;
swapped = true;
}
}
} while(swapped);
return array;
}
The error says: Sorting.html:59 Uncaught TypeError: Cannot read property '0' of undefined. I have this function on a button. Any kind of help would be nice! Thank you
Running your code exactly as it is, I get:
Cannot read property '1' of undefined
This is because in your comparison, you're attempting to compare array[a][1] > array[a+1][1], and this works except for the last loop, where array[a+1] doesn't exist, and because it doesn't exist, 1 is undefined.
Here's a working solution, with a few notable differences.
I don't know why you had length, and element as parameters for your bubbleSort, but they're gone now
You can just use the array length in your for loop, that way if you add more items to your array, you don't have to update the 5 you had hardcoded.
In the for loop, I minus 1 from the array length so we're never trying to compare the last item against an item after it that doesn't exist.
Also, I used i instead of a, since that's a common variable name for an incrementer.
I defined swapped inside the do...while, otherwise you'll have created an infinite loop because swapped will get set to true on the first pass and stay true forever.
You don't have to return the array, as it is modifying the original in place when you call bubbleSort
var array = [
["Rober Hill", 123.54],
["Chrsitopher Reddkin", 54.67],
["Maggie Woods", 1000.87],
["Jennifer Jones", 3.34],
["Marcus Parker", 64.98]
];
function bubbleSort(array) {
do {
var swapped = false;
for (var i = 0; i < array.length - 1; i++) {
if (array[i][1] > array[i + 1][1]) {
var temp = array[i];
array[i] = array[i + 1];
array[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}
bubbleSort(array)
console.log(array);
Move var swapped = false; inside do {....
Also update condition in for it should be like a < 4; or it's better if you use generalized condition as a < array.length - 1;
var array = [
["Rober Hill", 123.54],
["Chrsitopher Reddkin", 54.67],
["Maggie Woods", 1000.87],
["Jennifer Jones", 3.34],
["Marcus Parker", 64.98]
];
function bubbleSort(array, length, element) {
do {
var swapped = false;
for (var a = 0; a < array.length - 1; a++) // Line 59
{
if (array[a][1] > array[a + 1][1]) {
var temp = array[a][1];
array[a][1] = array[a + 1][1];
array[a + 1][1] = temp;
swapped = true;
}
}
} while (swapped);
return array;
}
console.log(bubbleSort(array));

Comaparing a variable inside a loop

I'm trying to compare for some duplicate value inside a loop because I need to do some logic. My problem is that I can't get the output that I expected.
var tempVal;
for (i = 0; i < obj.length; i++) {
var items = obj[i];
tempVal = items.fund;
console.log(tempVal);
console.log(tempVal == tempVal);
if(tempVal == tempVal){
//do something
}
In my example I have 2 same value in tempVal variable. My console.log(tempVal == tempVal) returns true in first loop but I thought it would return null or undefined in first loop because there's nothing to compare because it's empty on the first loop. What I need is top first return false then true. Thanks
You are comparing the same variable, obviously they are equal, create another variable and compare.
tempVal = items.fund;
console.log(tempVal);
console.log(tempVal == tempVal); //both are same
You haven't defined the initial value. So obuse a variable compare with them self are always equal. Why are you not using another name for remove complexity also.
var tempVal;
for (i = 0; i < obj.length; i++) {
var items = obj[i];
temp = items.fund;
console.log(temp);
console.log(temp == tempVal);
if(temp == tempVal){
//do something
}
Looking at your code, I thought this is what you're trying to achieve:
var src = [
{
"index": 0,
"fund": 100
},
{
"index": 1,
"fund": 200
},
{
"index": 2,
"fund": 100
}];
var tempFunds = [];
const uniqueFunds = src.filter(item => {
if (tempFunds.indexOf(item.fund) === -1) {
tempFunds.push(item.fund);
return item;
}
});
// logs the array with unique funds
console.log(uniqueFunds);
You can implement an else branch above if you want to deal with duplicate fund(s). Although there is no issue with your choice to use a for loop, you could also consider map or filter function(s) based on your problem.

Javascript to validate json message

I'm working to keep validation to the incoming json message mentioned below.
"fields_group": [{
"index": 1,
"value": "test"
}, {
"index": 2,
"value": "test"
}, {
"index": 3,
"value": "test"
}, {
"index": 4,
"value": "test"
}, {
"index": 5,
"value": "test"
}]
Validations:
1) Index value should not be duplicate
2) Should allow indexes 1 to 5 only.
3) Make sure index exist for each value.
Can someone help me with Javascript that does the above in an optimal way? I tried with 2 for loops which is O(n2), but I need a faster solution.
You can use every() and add object as optional parameter to check for duplicate index values.
var obj = {"fields_group":[{"index":1,"value":"test"},{"index":2,"value":"test"},{"index":3,"value":"test"},{"index":4,"value":"test"},{"index":5,"value":"test"}]}
var result = obj.fields_group.every(function(e) {
if(!this[e.index] && e.index <= 5 && e.index > 0 && e.index) {
this[e.index] = true;
return true;
}
}, {});
console.log(result)
You can also use regular expression /^[1-5]$/ to check index values.
var obj = {"fields_group":[{"index":1,"value":"test"},{"index":2,"value":"test"},{"index":3,"value":"test"},{"index":4,"value":"test"},{"index":5,"value":"test"}]}
var result = obj.fields_group.every(function(e) {
if(!this[e.index] && /^[1-5]$/.exec(e.index)) {
this[e.index] = true;
return true;
}
}, {});
console.log(result)
Use the following approach with Array.map, Array.some and RegExp.test functions :
var obj = {"fields_group":[{"index":1,"value":"test"},{"index":2,"value":"test"},{"index":3,"value":"test"},{"index":4,"value":"test"},{"index":5,"value":"test"}]}
var isValid = function(obj){
var indexes = obj.map(function(v){ return v.index; });
return !indexes.some(function(v, k, a){ return a.lastIndexOf(v) !== k; })
&& indexes.length === indexes.map(Boolean).length
&& /^[1-5]+$/.test(indexes.join(""));
}
console.log(isValid(obj.fields_group)); // true
!indexes.some(function(v, k, a){ return a.lastIndexOf(v) !== k; }) - ensures that all indexes are unique
indexes.length === indexes.map(Boolean).length - ensures that each index value exists(not empty)
/^[1-5]+$/.test(indexes.join("") - ensures that there's should be indexes in range from 1 to 5 only
Another method:
function validate(fields_group) {
if (fields_group.length > 5) {
console.log("The array has more than 5 elements. The max is 5.");
return false;
}
var idxs = {};
for (var i = 0; i < fields_group.length; i++) {
var obj = fields_group[i];
if (obj.index == null || idxs[obj.index] || obj.index < 1 || obj.index > 5) {
console.log("An object does not have a valid index.");
return false;
} else {
idxs[obj.index] = true;
}
}
console.log("The feilds group is valid.");
return true;
}
I have measured the execution time (using performace.now() on Chrome) for the answers listed, and found this to be the fastest.

Deferred assignment in pure javascript

In this question I encountered the following simplified problem:
We start with an array of Objects with a value attribute. We want to calculate for each value what percentage of the sum of values it is, and add it to the structure as a property. To do this, we need to know the sum of values, but this sum is not calculated beforehand.
//Original data structure
[
{ "value" : 123456 },
{ "value" : 12146 }
]
//Becomes
[
{
"value" : 123456,
"perc" : 0.9104
},
{
"value" : 12146 ,
"perc" : 0.0896
}
]
An easy, and probably most readable, solution is to go through the data structure twice. First we calculate the sum, then we calculate the percentage and add it to the data structure.
var i;
var sum = 0;
for( i = 0; i < data.length; i++ ) {
sum += data[i].value;
}
for( i = 0; i < data.length; i++ ) {
data[i].perc = data[i].value / sum;
}
Can we instead just go through the data structure once, and somehow tell that the percentage expression should only be evaluated once the entire sum is known?
I am primarily interested in answers that address pure javascript. That is: Without any libraries.
A solution with self-modifying code.
It moves the function f for the calculation to the end of the iteration and then it goes through the chained functions for the assignments of the percentage of the single items.
var data = [
{ "value": 123456 },
{ "value": 12146 },
];
data.reduceRight(function (f, a, i) { // reduceRight, i=0 is at the end of reduce required
var t = f; // temporary save previous value or function
f = function (s) { // get a new function with sum as parameter
a.perc = a.value / s; // do the needed calc with sum at the end of reduce
t && t(s); // test & call the old func with sum as parameter
};
f.s = (t.s || 0) + a.value; // add value to sum and save sum in a property of f
i || f(f.s); // at the last iteration call f with sum as parameter
return f; // return the function
}, 0); // start w/ a value w/ a prop (undef/null don't work)
document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');
This solution uses a single loop to calculate the sum and place a computed perc property on each element using a getter:
function add_percentage(arr) {
var sum = 0;
arr.forEach(e => {
sum += e.value;
Object.defineProperty(e, "perc", {
get: function() { return this.value / sum; }
});
});
}
A straightforward deferral would just be
function add_percentage(arr) {
var sum = 0;
arr.forEach(e => {
sum += e.value;
setTimeout(() => e.perc = e.value / sum);
});
}
But, what is the point of doing this exactly?
A way to make this with one less loop is to write out the whole sum statement made up of all possible items, for instance
var sum = (data[0] ? data[0].value : 0) +
(data[1] ? data[1].value : 0) +
(data[2] ? data[2].value : 0) +
...
(data[50] ? data[50].value : 0);
for( i = 0; i < data.length; i++ ) {
data[i].perc = data[i].value / sum;
}
Not that this is actually a real solution
You could use Array's reduce function but that is still a loop in the background, and a function call for each array element:
var sum = data.reduce(function(output,item){
return output+item.value;
},0);
for( i = 0; i < data.length; i++ ) {
data[i].perc = data[i].value / sum;
}
You could use the ES6 Promise, but there you are still adding a bunch of function calls
var data = [
{ "value" : 123456 },
{ "value" : 12146 }
]
var sum = 0;
var rej = null;
var res = null;
var def = new Promise(function(resolve,reject){
rej = reject;
res = resolve;
});
function perc(total){
this.perc = this.value/total;
}
for( i = 0; i < data.length; i++ ) {
def.then(perc.bind(data[i]));
sum+=data[i].value;
}
res(sum);
Perf Tests
Addition statement
10,834,196
±0.44%
fastest
Reduce
3,552,539
±1.95%
67% slower
Promise
26,325
±8.14%
100% slower
For loops
9,640,800
±0.45%
11% slower
Looking at the problem some more, the desired effect is most easily reproduced using a stack. The easiest way of doing that here is by creating a recursive function instead of a loop. The recursive function will act as a loop, and the destacking can be used to set the percentage property.
/**
* Helper function for addPercentage
* #param arr Array of data objects
* #param index
* #param sum
* #return {number} sum
*/
function deferredAddPercentage(arr, index, sum) {
//Out of bounds
if (index >= arr.length) {
return sum;
}
//Pushing the stack
sum = deferredAddPercentage(arr, index + 1, sum + arr[index].value);
//Popping the stack
arr[index].percentage = arr[index].value / sum;
return sum;
}
/**
* Adds the percentage property to each contained object
* #param arr Array of data Objects
*/
function addPercentage(arr) {
deferredAddPercentage(arr, 0, 0);
}
// ******
var data = [{
"value": 10
}, {
"value": 20
}, {
"value": 20
}, {
"value": 50
}];
addPercentage(data);
console.log( data );
It will perform 29% worse than 2 simple for-loops. Extended Patrick's JSPerf.
The OP has already given an example of a recursive solution. Although I believe that a non-tail recursive function is the ideal approach for this task, I think their implementation has two drawbacks though:
It mutates state of its parent scope
It is very specific and thus hardly reusable
I'm trying to implement a more generic solution without mutating global state. Please note that I would usually solve this problem by combining several smaller, reusable functions. The OP's condition is, however, to have only a single loop. This is a fun challenge and my implementation isn't intended for being used in real code!
I call the function defmap that is, deferred map:
const xs = [
{ "value" : 10 },
{ "value" : 20 },
{ "value" : 20 },
{ "value" : 50 }
];
const defmap = red => map => acc => xs => {
let next = (len, acc, [head, ...tail]) => {
map = tail.length
? next(len, red(acc, head), tail)
: map([], red(acc, head), len);
return map(Object.assign({}, head));
};
return next(xs.length, acc, xs);
};
const map = f => (xs, acc, len) => o => xs.length + 1 < len
? map(f) (append(f(o, acc), xs), acc, len)
: append(f(o, acc), xs);
const append = (xs, ys) => [xs].concat(ys);
const reducer = (acc, o) => acc + o.value;
const mapper = (o, acc) => Object.assign(o, {perc: o.value / acc});
console.log(defmap(reducer) (map(mapper)) (0) (xs));
As per my comment, I can not see a way to do this without effectively looping over it twice.
To actually count the values
To evaluate each value against the total
To answer the "deferred" part of your question, one possible solution, albeit slower (Just guessing due to function call?) and probably not what you would want to use (JSFiddle):
var data = [
{ value: 10 },
{ value: 20 },
{ value: 20 },
{ value: 50 }
];
var total = 0;
for (var i = 0; i < data.length; i++) {
var current = data[i];
total += current["value"];
current.getPercent = function() { return this["value"] / total; };
}
for (var i = 0; i < data.length; i++) {
var current = data[i];
console.log(current.getPercent());
}
Outputs:
0.1
0.2
0.2
0.5
This has the added benefit of not actually calculating the value until you need it, but when it does calculate it, there will be a higher cpu cost (Due to calling the function etc).
This could be marginally optimised by changing the getPercent line to:
current.getPercent = function() {
return this["percent"] || (this["percent"] = this["value"] / total);
}
Which would ensure the calculation is only run the first time. Updated Fiddle
EDIT
I ran some tests (Forgot to save before I crashed chrome by testing too many iterations, but they are simple enough to replicate).
I was getting
Sumurai initial method (1000000 objects with value 0 -> 9999999) = 2200ms
My initial method (Same) = 3800ms
My "optimised" method (Same) = 4200ms

Categories

Resources