Why wont my calculator function complete the math? - javascript

Hi so I'm having trouble figuring out why my function will do the division but leave the multiplication as an array without completing the math. Here's the code:
const mathObj = {
"*": function(a , b) {return a * b},
"/": function(a , b) {return a / b},
"+": function(a , b) {return a + b},
"-": function(a , b) {return a - b}
}
const arr = [ 10, "/" , 2 , "*" , 10 , "/" , 2 ];
function solveTheMath(arr) {
const len = arr.length;
for(let i = 0 ; i < len ; i++){
if(arr[i] === "/" || arr[i] === "*"){
const sliced = arr.slice(i - 1 , i + 2);
var mathResult = mathObj[arr[i]](sliced[0], sliced[2]);
arr.splice(i - 1 , 3, mathResult);
console.log(arr);
//[5*5]
}
}
}
solveTheMath(arr);
Why doesn't the multiplication work but the division does?

My initial answer, while it did solve the issue, wasn't that correct. You wanted to use an iterative approach by the look of things (i.e. using a loop to navigate through the initial array and solve all operations before returning the result).
So I replied to you:
Both operations work, the problem is that you're only calling solveTheMath once.
You need to call your function again to solve the array you have constructed. If the constructed array is made of only one element that means the process has reached the end of the computing, you can, therefore, return the first (and only element) of the array.
You are solving the problem in a recursive manner:
const mathObj = {
"*": function(a , b) {return a * b},
"/": function(a , b) {return a / b},
"+": function(a , b) {return a + b},
"-": function(a , b) {return a - b}
}
const arr = [ 10, "/" , 2 , "*" , 10 , "/" , 2 ];
function solveTheMath(arr) {
const len = arr.length;
for(let i = 0 ; i < len ; i++){
if(arr[i] === "/" || arr[i] === "*"){
const sliced = arr.slice(i - 1 , i + 2);
var mathResult = mathObj[arr[i]](sliced[0], sliced[2]);
arr.splice(i - 1 , 3, mathResult);
if(arr.length == 1) {
return arr[0]; // <- no more calculations needed, return result
} else {
return solveTheMath(arr); // <- more calculations needed, call it again
};
}
}
}
console.log(solveTheMath(arr))
But actually that isn't correct, you can use both approaches: recursive and iterative to solve this problem. My initial answer provided a poor solution: I kept your for loop and called the function again to solve the remaining operations that were in the array. That wasn't necessary because the for loop only looped to find the second item and stopped. Anyway, here's a clearer answer, highlighting both approaches.
Note: I have renamed solveTheMath to calculate and mathObj to operations.
Iterative approach
That's the approach you went for with your question. Because you are using a for loop to calculate all operations on a single function call (so the function isn't calling itself over and over).
I recommend using a while loop for this because **you will have a hard time looping arr when it gets modified (you are replacing three elements with one on each loop).
I'll take the array [10, "/", 2, "*", 10, "/", 2] as the starting array to show the process step by step. You can solve the first operation of the provided array. For example, given: , calculate will calculate the first operation here: 10, "/", 2
While the array contains more than one element we will do the following:
the first three elements of the array contain: two factors and an operator sign. By slicing the array we can extract those values and save them. I'm using a destructuring assignment to make it more verbose:
const [a, operator, b] = arr.slice(0, 3);
here a = 10, operator = "/" and b = 2
we will calculate the result of this operation with this line:
const result = operations[operator](a, b);
result = 5 (cf: 10 / 2)
then replace the first three elements of the array with the integer result:
arr.splice(0, 3, result);
at this point, arr is equal to [5, "*", 10, "/", 2]
The block has been executed, the while condition is checked again. arr does contain more than one element so the block is executed again. Remember, at this point arr is equal to [5, "*", 10, "/", 2], not to [10, "/", 2, "*", 10, "/", 2] (we are making progress in the calculation).
At the end of the second loop we have arr that is equal to [50, "/", 2].
A loop after that it is equal to [25].
The while condition isn't met anymore because arr only contains one element, the while loop has stopped and the result can be returned.
const operations = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b
}
const calculate = arr => {
while(arr.length > 1) { // <- while there are calculations to be done, repeat this block
const [a, operator, b] = arr.slice(0, 3);
const result = operations[operator](a, b);
arr.splice(0, 3, result);
}
return arr[0]; // <- no more operations to be done, return result
}
console.log(calculate(
[10, "/", 2, "*", 10, "/", 2]
));
Recursive approach
We can use a recursive approach: the function will only calculate the first operation of the provided array and return a new array with the result of this first operation.
Here is an example:
Same as in the iterative array, given the input [10, "/", 2, "*", 10, "/", 2] we will first take the first two factors and operator sign by slicing the array. Then we will calculate the result of the operation. Finally, we'll replace the first three elements of the array with this result:
const [a, operator, b] = arr.slice(0, 3);
const result = operations[operator](a, b);
arr.splice(0, 3, result);
then we check the length of this array:
if it contains only one element, it can be returned
else if it doesn't (in our case) we call the function again (this time on [5, "*", 10, "/", 2]).
So the function runs again with a new input and arr becomes [50, "/", 2] which has more than one element so the function needs to be called again (with [50, "/", 2] as input)
Now, arr is [25] it only contains one element, the result can be returned (25).
const operations = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b
}
const calculate = arr => {
const [a, operator, b] = arr.slice(0, 3);
const result = operations[operator](a, b);
arr.splice(0, 3, result);
if (arr.length == 1) {
return arr[0]; // <- no more calculations needed, return result
} else {
return calculate(arr); // <- more calculations needed, call it again
}
}
console.log(calculate(
[10, "/", 2, "*", 10, "/", 2]
));
Going further...
You can see both methods are quite similar: the main process is the same but the way they handle the end of execution is different. In this case, both are reasonable to use. The iterative approach may seem more natural to you at first. However remember that recursion can allow you to solve more complicated problems. For example, if you would like to implement a kind parenthesis system in your function:
How would you go about calculating: 10*(2+2)/2? calculate([10, "*", 2, "+", 2, "/", 2]) would obviously return 11...
Take the input [[10, "+", 2], "/", 2] instead, that makes more sense! How can we calculate the correct result?
Well with our recursive approach this can be implemented quite easily: if a or/and b are arrays then we reassign them by calling calculate on them. That's all:
if(a.constructor == Array) {
a = calculate(a);
}
if(b.constructor == Array) {
b = calculate(b);
}
const operations = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b
}
const calculate = arr => {
let [a, operator, b] = arr.slice(0, 3);
if(a.constructor == Array) {
a = calculate(a);
}
if(b.constructor == Array) {
b = calculate(b);
}
const result = operations[operator](a, b);
arr.splice(0, 3, result);
if (arr.length == 1) {
return arr[0]; // <- no more calculations needed, return result
} else {
return calculate(arr); // <- more calculations needed, call it again
}
}
console.log(calculate(
[[10, "+", 2], "/", 2]
));
Adding those two if blocks in the while loop of the iterative approach would have worked. But then you would be left with a... recursive function. That's why you may want to go straight off with the recursive approach. That allows you to expand your code more easily.
More reference on recursion
Recursion in JavaScript, freecodecamp.org
How factorial's recursive implementation works, wikimedia
Understanding recursive functions in JavaScript, medium.com
Scope and the function stack: recursion, MDN web docs

Related

Create a list of results of all recursive calls performed by a function call

I want to achieve the same result I can get with this code:
function fibs(n) {
let fibs = []
for (let i = 0; i <= n; i++) {
if ((i <= 1)) fibs.push(i)
else fibs.push(fibs[i - 1] + fibs[i - 2])
}
return fibs
}
console.log( fibs(8) )
with a recursive function.
Obviously, when you console.log(fibs(8) it renders a list like this: [0, 1, 1, 2, 3, 5, 8, 13, 21]
My recursive function looks like this:
function fibsRec(n) {
if (n < 2) return n
return fibsRec(n - 1) + fibsRec(n - 2)
}
console.log( fibsRec(8) )
and if you console.log(fibsRec(8)) it returns 21, which is the 8th Fibonacci number, but doesn't give me the list of all the Fibonacci numbers before it. How can I get the list without a loop, just from my recursive function?
How can I get the same outcome as fibs() with fibsRec()
where it goes wrong
Let's review. If fibsRec is meant to return an array, we can first notice that return n isn't going to work. n is just a number and we need an array.
function fibsRec(n) {
if (n < 2) return n // <- problem one
return fibsRec(n - 1) + fibsRec(n - 2) // <- problem two
}
Second, if fibsRec is going to be returning arrays, we cannot simply call + for fibsRec(n - 1) and fibsRec(n - 2). Watch what happens if we were to try -
const a = [1,2,3]
const b = [4,5,6]
console.log(a + b) // 1,2,34,5,6
Maybe you're thinking that's an odd result. Really JavaScript should throw an error for such misuse of +, but instead it tries its "best" to perform the addition. To do so, it coerces each array to a string first, then combines the strings together -
const a = [1,2,3]
const b = [4,5,6]
console.log(String(a)) // 1,2,3
console.log(String(b)) // 4,5,6
console.log(a + b) // 1,2,34,5,6
behaviour-oriented design
To understand how fibsRec needs to behave, let's first define some outputs for known inputs -
f(n)
output
f(0)
[]
f(1)
[0]
f(2)
[0,1]
f(3)
[0,1,1]
f(4)
[0,1,1,2]
f(5)
[0,1,1,2,3]
f(6)
[0,1,1,2,3,5]
...
...
To fix the first problem, easy mode, change return n to return a range 0..n instead -
function fibsRec(n) {
if (n < 2) return range(0,n) // <- fix one
return fibsRec(n - 1) + fibsRec(n - 2) // ...
}
const range = (a, b) =>
a >= b
? []
: [a, ...range(a + 1, b)]
you can't + arrays, but you can fibplus them...
To fix the second problem, we need a function that can "add" fibonacci sequences (arrays) because + just isn't going to cut it. We'll call our function fibplus -
function fibsRec(n) {
if (n < 2) return range(0,n)
return fibplus(fibsRec(n - 1), fibsRec(n - 2)) // <- fix two
}
We just have to define how fibplus will add the sequences to achieve the correct result. Let's work off an example. To compute fib(6) we need to "add" fib(5) and fib(4). We could just try stacking the two sequences and adding down to get the result -
0 1 1 2 3 == fib(4)
+ 0 1 1 2 3 5 == fib(5)
------------------------------------
0 1 2 3 5 8 ~~ fib(6)
It's very close to fib(6) but notice it's off by one. Watch what happens when we prepend a 1 to the smaller number before adding -
1 -> 1 0 1 1 2 3
+ 0 1 1 2 3 5
------------------------------------
1 1 2 3 5 8 ~~ fib(6)
Now if we prepend a 0 to the sum ...
1 0 1 1 2 3
+ 0 1 1 2 3 5
------------------------------------
0 -> 0 1 1 2 3 5 8 == fib(6)
We now have fib(6)! We just need to write fibplus to implement this adding technique -
const fibplus = (a, b) =>
[0, ...zip(add, a, [1, ...b])]
const zip = (f, a, b) =>
a.map((v, i) => f(v, b[i]))
const add = (a, b) => a + b
functioning demo
Run the snippet below to verify the result in your own browser -
const fib = n =>
n < 2
? range(0, n)
: fibplus(fib(n - 1), fib(n - 2))
const range = (a, b) =>
a >= b
? []
: [a, ...range(a + 1, b)]
const fibplus = (a, b) =>
[0, ...zip(add, a, [1, ...b])]
const zip = (f, a, b) =>
a.map((v, i) => f(v, b[i]))
const add = (a, b) => a + b
console.log(String(fib(20)))
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181
visualizing
So indeed we were able to make fibsRec work using fibplus, but by mirroring the original recursive process we inherited a lot of inefficiency as well. We can see the sheer amount of duplicated work -
#WillNess comments below and explains another way fibplus can be rewritten to save some work, but the real drawback of the approach above is the resulting exponential process. Let's see some other ways to get the result we are looking for.
other processes
I like the way you asked the question: "How can I get the same outcome?". Different procedures evolve different processes, and we are not required to create a recursive branching process. Instead a linear iterative process is more efficient and better suited for the desired output.
Note fibs returns an array, but I cast the output as a string for more digestible output -
const fibs = (n, a = 0, b = 1) =>
n <= 0
? []
: [a, ...fibs(n - 1, b, a + b)]
console.log(String(fibs(10)))
So how does it work? Recursion is a functional heritage and so using it with functional style yields the best results. This means avoiding things like mutation, variable reassignments, or other side effects. When a function is referentially transparent, its call can be replaced by its return value, without changing the meaning of our program.
fibs(6)
== fibs(6, 0, 1)
== [0, ...fibs(5, 1, 1)]
== [0, ...[1, ...fibs(4, 1, 2)]]
== [0, ...[1, ...[1, ...fibs(3, 2, 3)]]]
== [0, ...[1, ...[1, ...[2, ...fibs(2, 3, 5)]]]]
== [0, ...[1, ...[1, ...[2, ...[3, ...fibs(1, 5, 8)]]]]]
== [0, ...[1, ...[1, ...[2, ...[3, ...[5, ...fibs(0, 8, 13)]]]]]]
== [0, ...[1, ...[1, ...[2, ...[3, ...[5, ...[]]]]]]]
== [0, ...[1, ...[1, ...[2, ...[3, ...[5]]]]]]
== [0, ...[1, ...[1, ...[2, ...[3, 5]]]]]
== [0, ...[1, ...[1, ...[2, 3, 5]]]]
== [0, ...[1, ...[1, 2, 3, 5]]]
== [0, ...[1, 1, 2, 3, 5]]
== [0, 1, 1, 2, 3, 5]
wasteful intermediate arrays
You might notice that the many intermediate arrays is somewhat wasteful and the result could be accomplished with a single array. Let's make a push helper to do just that -
const push = (arr, val) =>
(arr.push(val), arr)
const fibs = (n, a = 0, b = 1, r = []) =>
n == 0
? r
: fibs(n - 1, b, a + b, push(r, a))
console.log(String(fibs(10)))
Let's see how this one works -
fibs(6)
== fibs(6, 0, 1, [])
== fibs(5, 1, 1, [0])
== fibs(4, 1, 2, [0,1])
== fibs(3, 2, 3, [0,1,1])
== fibs(2, 3, 5, [0,1,1,2])
== fibs(1, 5, 8, [0,1,1,2,3])
== fibs(0, 8, 11, [0,1,1,2,3,5])
== [0,1,1,2,3,5]
streams
Another fun way to calculate sequences of fibonacci numbers is to use streams. Streams deliver data over time as it is needed, instead of all at once. Because streams allow us to consume only as much as need, we can actually define fibs as an infinite stream. Notice it is no longer a function -
const fibs =
stream(0, _ =>
stream(1, _ =>
streamAdd(fibs, fibs.next)))
The building blocks of our streams are emptyStream and stream. To construct a non-empty stream, we give provide any value to stream and a thunk _ => ... where ... is computation of the next value, if any -
const emptyStream =
Symbol('emptyStream')
const stream = (value, next) => ({
value,
get next() { delete this.next; return this.next = next() }
})
Streams as defined here are not the same as JavaScript's built-in generators. The primary difference is they are persistent, meaning they can be replayed any number of times. JavaScript's generators have an internal "cursor" and once it advances, you can never rewind it. This is important for our fibs stream because you can see it consumes itself twice. If we used generators, advancing the generator for one operation would permanently advance it for all others.
Next we define generic stream operations. streamAdd combines two streams of numbers using addition -
const streamAdd = (s1, s2) =>
s1 === emptyStream || s2 === emptyStream
? emptyStream
: stream(s1.value + s2.value, _ => streamAdd(s1.next, s2.next))
And because fibs is infinite, we need some way to limit how much we bite off. streamTake will terminate an infinite stream after that limit is reached -
const streamTake = (s = emptyStream, n = 0) =>
s === emptyStream || n <= 0
? emptyStream
: stream(s.value, _ => streamTake(s.next, n - 1))
Finally, to fulfill the desired output, we convert the finite stream to an array -
function streamToArray(s = emptyStream) {
const r = []
while (s != emptyStream) {
r.push(s.value)
s = s.next
}
return r
}
Run the stream demo below to verify the result in your browser -
const emptyStream =
Symbol('emptyStream')
const stream = (value, next) => ({
value,
get next() { delete this.next; return this.next = next() }
})
const streamAdd = (s1, s2) =>
s1 === emptyStream || s2 === emptyStream
? emptyStream
: stream(s1.value + s2.value, _ => streamAdd(s1.next, s2.next))
const streamTake = (s = emptyStream, n = 0) =>
s === emptyStream || n <= 0
? emptyStream
: stream(s.value, _ => streamTake(s.next, n - 1))
function streamToArray(s = emptyStream) {
const r = []
while (s != emptyStream) {
r.push(s.value)
s = s.next
}
return r
}
const fibs =
stream(0, _ =>
stream(1, _ =>
streamAdd(fibs, fibs.next)))
console.log(String(streamToArray(streamTake(fibs, 20))))
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181
I would do this like this (it's also a little bit faster because of caching):
function fibsRec(n) {
const cache = {
1: 1,
0: 1
}
rec(n)
return Object.values(cache)
function rec(n) {
if (cache[n]) return cache[n]
cache[n - 1] ??= rec(n - 1)
cache[n - 2] ??= rec(n - 2)
return cache[n - 1] + cache[n - 2]
}
}
console.log(fibsRec(8))
Of course the easy answer would be to make a wrapper function that loops and calls fibsRec(i) each time, but that's not what you're looking for.
First, you need to think about what fibsRec is doing to see why this isn't naturally as simple as it sounds. As you already know it gets the nth fibonacci number by finding the (n-1)th and (n-2)th and to get those, it keeps going further back.
But that means that to get the n-1 and n-2 numbers, you need to generate the sequence to n-1 and n-2, not only that, but when you start generating that sequence for, let's say n-1, and you have to calculate it's previous indices, then you need two more sequences, and so on and so on. It's extremely inefficient.
But the reason I'm bringing this up, is to say that we can't just create an empty array and have it push the number we'd return before returning it, because we're making so many sequences, our array is gonna contain all those results.
Look at this:
function fibArray(n) {
const output = [];
function fibsRec(n) {
if (n < 2) {
output.push(n)
return n;
}
let num = fibsRec(n - 2) + fibsRec(n - 1)
output.push(num);
return num;
}
fibsRec(n);
return output
}
console.log( fibArray(8) )
See how many times we're calculating a number on the fibonacci sequence?
We definitely can't directly use this approach. But what we can use is dynamic programming. What we'll do is, memoize (save) each fibonacci number we calculate to a dictionary, and the next time we're looking for it, instead of recursing a new sequence, we'll just get it from the dictionary directly.
That way, we're getting each fibonacci number only once. So when we calculate it, we can push it to our output array, and it'll be a clean fibonacci sequence.
function fibArray(n) {
const output = [];
const fibs = {}; // Create memo (a dictionary)
function fibsRec(n) {
if (fibs[n]) return fibs[n]; // Check memo
if (n < 2) {
fibs[n] = n;
output.push(n)
return n;
}
let num = fibsRec(n - 2) + fibsRec(n - 1) // Start with n-2 to eventually call fibsRec(0) before fibsRec(1) and push them in that order
fibs[n] = num; // Memoize
output.push(num);
return num;
}
fibsRec(n);
return output
}
console.log( fibArray(8) )

Ways to get sum of two lowest positive integers (JS debugging failure)

I was doing this Codewar challenge of getting sum of two smallest integer.
so I was trying to solve with this:
function sumTwoSmallestNumbers(numbers) {
const m = numbers.sort( (a, b) => a - b )[2];
const a = numbers.filter(v => v < numbers.sort( (a, b) => a - b )[2]);
const b = a.reduce( (acc, n) => acc + n);
return b
};
It works, and later on I feel that const m is redundant, so I commented the whole const m line and then it doesn't work now, by "doesn't work" I mean it's giving incorrect outcome, not typeErrors.
My question is, how is that possible??
I mean on const a I literally rewrote what const m was referring to.
How is deleting something that is repeated affects the outcome?
Thanks in advance, I appreciate your time.
Simply, it is because the function Array.prototype.sort in javascript modifies the actual contents of the same instance of the array as well as returns the reference to the same array, In other words, it does not return a new array with the modified contents, it modifies the same array. That's why it doesn't work when you remove the line :
const m = numbers.sort( (a, b) => a - b )[2];
But, why doesn't it work when you tried to put the same logic with the next line of code?
const a = numbers.filter(v => v < numbers.sort( (a, b) => a - b )[2]);
The issue happens when the smallest value of the array is at an index larger than 2, for example:
[ 15, 28, 4, 2, 43 ]
when we try to execute the code on this array, lets take it a step by step to catch where it gets wrong.
function sumTwoSmallestNumbers(numbers) {
const a = numbers.filter(v => v < numbers.sort( (a, b) => a - b )[2]);
const b = a.reduce( (acc, n) => acc + n);
return b
};
const arr = [ 15, 28, 4, 2, 43 ]
const result = sumTwoSmallestNumbers(arr)
console.log(result)
When executing the previous code, the first thing the function will do is the filtering.
It will iterate through the elements of the array:
Starting from index 0 => arr[0] = 15
Testing the condition: 15 < numbers.sort( (a, b) => a - b )[2] => (15 < 15) => false
Notice! as I explained earlier, that the function sort will change the actual array rather than generating a new one, that means that when attempting to test the condition the first time, the array will be sorted, which means that if it is not already sorted, the order of its elements will defiantly change, which is a bad thing considering that the change happened during an ongoing loop through the arrays elements.
Before continuing the iteration of filter, let's make it clear that the new order of the array after the sorting has been executed when attempting to check the condition is as follows:
[ 2, 4, 15, 28, 43 ]
Index 1 => arr[1] = 4
Testing the condition: 4 < numbers.sort( (a, b) => a - b )[2] => (4 < 15) => true
Index 2 => arr[2] = 15
Testing the condition: 15 < numbers.sort( (a, b) => a - b )[2] => (15 < 15) => false
... The rest of the elements
Conclusion
As you can notice, the value 15 has been checked twice, and the value 2, has not been checked at all, which is because of the sudden change in the order of the array during the currently ongoing iteration of the filter method. Which has lead to placing the value of 2 in an already checked location, and the value of 15 into a location that has not been checked yet.
! Notice This is not an optimal solution to such a problem, it does too much work to get to the solution, (filtering, sorting, reducing), which are expensive operations to execute.
A better solution will be something like this, which involves a single loop through the elements of the array O(n):
function sumTwoSmallestNumbers(numbers) {
let lowest = Math.min(numbers[0], numbers[1]),
secondLowest = Math.max(numbers[0], numbers[1])
for(let i = 2; i < numbers.length; i++){
if(numbers[i] < lowest){
secondLowest = lowest
lowest = numbers[i]
}else if(numbers[i] < secondLowest)
secondLowest = numbers[i]
else continue
}
return lowest + secondLowest
}

Trying to solve using recursion without using other algorithms

I am trying to get better at understanding recursion so that I can get better at implementing dynamic programming principles. I am aware this problem can be solved using Kadane's algorithm; however, I would like to solve it using recursion.
Problem statement:
Given an array of integers, find the subset of non-adjacent elements with the maximum sum. Calculate the sum of that subset.
I have written the following partial solution:
const maxSubsetSum = (arr) => {
let max = -Infinity
const helper = (arr, len) => {
if (len < 0) return max
let pointer = len
let sum = 0
while (pointer >= 0) {
sum += arr[pointer]
pointer -= 2
}
return max = Math.max(sum, helper(arr, len - 1))
}
return helper(arr, arr.length - 1)
}
If I had this data:
console.log(maxSubsetSum([3, 5, -7, 8, 10])) //15
//Our subsets are [3,-7,10], [3,8], [3,10], [5,8], [5,10] and [-7,10].
My algorithm calculates 13. I know it's because when I start my algorithm my (n - 2) values are calculated, but I am not accounting for other subsets that are (n-3) or more that still validate the problem statement's condition. I can't figure out the logic to account for the other values, please guide me on how I can accomplish that.
The code is combining recursion (the call to helper inside helper) with iteration (the while loop inside helper). You should only be using recursion.
For each element of the array, there are two choices:
Skip the current element. In this case, the sum is not changed, and the next element can be used. So the recursive call is
sum1 = helper(arr, len - 1, sum)
Use the current element. In this case, the current element is added to the sum, and the next element must be skipped. So the recursive call is
sum2 = helper(arr, len - 2, sum + arr[len])
So the code looks like something this:
const maxSubsetSum = (arr) => {
const helper = (arr, len, sum) => {
if (len < 0) return sum
let sum1 = helper(arr, len - 1, sum)
let sum2 = helper(arr, len - 2, sum + arr[len])
return Math.max(sum1, sum2)
}
return helper(arr, arr.length - 1, 0)
}
Your thinking is right in that you need to recurse from (n-2) once you start with a current index. But you don't seem to understand that you don't need to run through your array to get sum and then recurse.
So the right way is to
either include the current item and recurse on the remaining n-2 items or
not include the current item and recurse on the remaining n-1 items
Lets look at those two choices:
Choice 1:
You chose to include the item at the current index. Then you recurse on the remaining n-2 items. So your maximum could be item itself without adding to any of remaining n-2 items or add to some items from n-2 items.
So Math.max( arr[idx], arr[idx] + recurse(idx-2)) is the maximum for this choice. If recurse(idx-2) gives you -Infinity, you just consider the item at the current index.
Choice 2:
You didn't choose to include the item at the current index. So just recurse on the remaining n-1 items - recurse(n-1)
The final maximum is maximum from those two choices.
Code is :
const maxSubsetSum = (arr) => {
let min = -Infinity
const helper = (arr, idx) => {
if ( idx < 0 ) return min
let inc = helper(arr, idx-2)
let notInc = helper(arr, idx-1)
inc = inc == min ? arr[idx] : Math.max(arr[idx], arr[idx] + inc)
return Math.max( inc, notInc )
}
return helper(arr, arr.length - 1)
}
console.log(maxSubsetSum([-3, -5, -7, -8, 10]))
console.log(maxSubsetSum([-3, -5, -7, -8, -10]))
console.log(maxSubsetSum([-3, 5, 7, -8, 10]))
console.log(maxSubsetSum([3, 5, 7, 8, 10]))
Output :
10
-3
17
20
For the case where all items are negative:
In this case you can say that there are no items to combine together to get a maximum sum. If that is the requirement the result should be zero. In that case just return 0 by having 0 as the default result. Code in that case is :
const maxSubsetSum = (arr) => {
const helper = (arr, idx) => {
if ( idx < 0 ) return 0
let inc = arr[idx] + helper(arr, idx-2)
let notInc = helper(arr, idx-1)
return Math.max( inc, notInc )
}
return helper(arr, arr.length - 1)
}
With memoization:
You could memoize this solution for the indices you visited during recursion. There is only one state i.e. the index so your memo is one dimensional. Code with memo is :
const maxSubsetSum = (arr) => {
let min = -Infinity
let memo = new Array(arr.length).fill(min)
const helper = (arr, idx) => {
if ( idx < 0 ) return min
if ( memo[idx] !== min) return memo[idx]
let inc = helper(arr, idx-2)
let notInc = helper(arr, idx-1)
inc = inc == min ? arr[idx] : Math.max(arr[idx], arr[idx] + inc)
memo[idx] = Math.max( inc, notInc )
return memo[idx]
}
return helper(arr, arr.length - 1)
}
A basic version is simple enough with the obvious recursion. We either include the current value in our sum or we don't. If we do, we need to skip the next value, and then recur on the remaining values. If we don't then we need to recur on all the values after the current one. We choose the larger of these two results. That translates almost directly to code:
const maxSubsetSum = ([n, ...ns]) =>
n == undefined // empty array
? 0
: Math .max (n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns))
Update
That was missing a case, where our highest sum is just the number itself. That's fixed here (and in the snippets below)
const maxSubsetSum = ([n, ...ns]) =>
n == undefined // empty array
? 0
: Math .max (n, n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns))
console.log (maxSubsetSum ([3, 5, -7, 8, 10])) //15
But, as you note in your comments, we really might want to memoize this for performance reasons. There are several ways we could choose to do this. One option would be to turn the array we're testing in one invocation of our function into something we can use as a key in an Object or a Map. It might look like this:
const maxSubsetSum = (ns) => {
const memo = {}
const mss = ([n, ...ns]) => {
const key = `${n},${ns.join(',')}`
return n == undefined
? 0
: key in memo
? memo [key]
: memo [key] = Math .max (n, n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns))
}
return mss(ns)
}
console.log (maxSubsetSum ([3, 5, -7, 8, 10])) //15
We could also do this with a helper function that acted on the index and memoized using the index for a key. It would be about the same level of complexity.
This is a bit ugly, however, and perhaps we can do better.
There's one issue with this sort of memoization: it only lasts for the current run. It I'm going to memoize a function, I would rather it holds that cache for any calls for the same data. That means memoization in the definition of the function. I usually do this with a reusable external memoize helper, something like this:
const memoize = (keyGen) => (fn) => {
const cache = {}
return (...args) => {
const key = keyGen (...args)
return cache[key] || (cache[key] = fn (...args))
}
}
const maxSubsetSum = memoize (ns => ns .join (',')) (([n, ...ns]) =>
n == undefined
? 0
: Math .max (n, n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns)))
console.log (maxSubsetSum ([3, 5, -7, 8, 10])) //15
memoize takes a function that uses your arguments to generate a String key, and returns a function that accepts your function and returns a memoized version of it. It runs by calling the key generation on your input, checks whether that key is in the cache. If it is, we simply return it. If not, we call your function, store the result under that key and return it.
For this version, the key generated is simply the string created by joining the array values with ','. There are probably other equally-good options.
Note that we cannot do
const recursiveFunction = (...args) => /* some recursive body */
const memomizedFunction = memoize (someKeyGen) (recursiveFunction)
because the recursive calls in memoizedFunction would then be to the non-memoized recursiveFunction. Instead, we always have to use it like this:
const memomizedFunction = memoize (someKeyGen) ((...args) => /* some recursive body */)
But that's a small price to pay for the convenience of being able to simply wrap up the function definition with a key-generator to memoize a function.
This code was accepted:
function maxSubsetSum(A) {
return A.reduce((_, x, i) =>
A[i] = Math.max(A[i], A[i-1] | 0, A[i] + (A[i-2] | 0)));
}
But trying to recurse that far, (I tried submitting Scott Sauyet's last memoised example), I believe results in run-time errors since we potentially pass the recursion limit.
For fun, here's bottom-up that gets filled top-down :)
function f(A, i=0){
if (i > A.length - 3)
return A[i] = Math.max(A[i] | 0, A[i+1] | 0);
// Fill the table
f(A, i + 1);
return A[i] = Math.max(A[i], A[i] + A[i+2], A[i+1]);
}
var As = [
[3, 7, 4, 6, 5], // 13
[2, 1, 5, 8, 4], // 11
[3, 5, -7, 8, 10] // 15
];
for (let A of As){
console.log('' + A);
console.log(f(A));
}

Find average of each array within an array

I'm trying to write a map/reduce to get the average of each array within an array.
For example.
[[1][2,3][4,5,6,7]] => [1, 2.5, 5.5]
Right now this is my code where result is the array of arrays:
result.map(array => {
return array.reduce((a, b) => (a + b)) / array.length;
})
const result = [
[1],
[2, 3],
[4, 5, 6, 7]
]
console.log(result.map(array => {
return array.reduce((a, b) => (a + b)) / array.length;
}))
Any help to get the desired output is much appreciated. As it stands, my output is reducing to an array of NaN's instead of the averages.
You need a closing parentesis.
By using Array#reduce with arrays with unknown length, you need to take a start value, which is in this case of a length of zero the result.
var result = [[1], [2, 3], [4, 5, 6, 7]],
avg = result.map(array => array.reduce((a, b) => a + b, 0) / array.length);
// ^^^ ^
// optional required
console.log(avg);
you must provide a second argument to the reduce function, the initial value of a. So:
result.map(array => {
return array.reduce((a, b) => a + b, 0) / array.length;
});
You may also want to ensure that array.length > 0 before you divide by it

selecting the two biggest values in an array

I'm trying to write a function that finds the two biggest value inside an array of numbers and stores them inside a new array. I'm unable to first remove the first biggest number from the original array and then find the second biggest.
here is my code:
function choseBig (myArray) {
//var myArray = str.split(" ");
var result = [];
var firstBig;
var secondBig;
// select the biggest value
firstBig = Math.max.apply(Math, myArray);
// find its index
var index = myArray.indexOf(firstBig);
// remove the biggest value from the original array
var myArray_2 = myArray.slice((index -1), 1);
// choose the second biggest value
secondBig = Math.max.apply(Math, myArray_2);
// push the results into a new array
result.push(firstBig, secondBig);
return result;
}
console.log(choseBig ([1,2,3,4,5,9]));
At first glance, I'd suggest:
function choseBig(myArray) {
return myArray.sort((a, b) => b - a).slice(0, 2);
}
console.log(choseBig([1, 2, 3, 4, 5, 9]));
To extend the above a little, for example offering the user the option to specify whether the returned values should be the highest numbers, or the lowest numbers, and how many they wish returned, I'd offer the following:
function choseBig(myArray, opts) {
// 'max': Boolean,
// true: returns the highest numbers,
// false: returns the lowest numbers
// 'howMany': Number,
// specifies how many numbers to return:
var settings = {
'max': true,
'howMany': 2
};
// ensuring we have an Object, otherwise
// Object.keys( opts ) returns an error:
opts = opts || {};
// retrieving the keys of the opts Object, and
// uses Array.prototype.forEach() to iterate over
// those keys; 'o' (in the anonymous function) is
// the array element (the property-name/key) from
// the array Object keys over which we're iterating:
Object.keys(opts).forEach(function(o) {
// updating the settings Object to the new values
// (if any is specified) to those set in the user-
// supplied opts Object:
settings[o] = opts[o];
});
// here we first sort the Array, using a numeric sort;
// using ES2015 Arrow functions. 'a' and 'b' are supplied
// by Array.prototype.sort() and refer to the current ('a')
// and next ('b') array-elements. If b - a is less than zero
// b is moved to a lower index; if a - b is less than zero
// a is moved to a lower index.
// Here we use a ternary operator based on whether settings.max
// is true; if it is true we sort to move the larger number to
// the lower index; otherwise we sort to move the smaller number
// to the lower index.
// Then we slice the resulting array to return the numbers from
// the 0 index (the first number) to the settings.howMany number
// (the required length of the array).
// this is then returned to the calling context.
return myArray.sort((a, b) => settings.max === true ? b - a : a - b).slice(0, settings.howMany);
}
console.log(choseBig([1, 2, 3, 4, 5, 9], {
// here we specify to select the largest numbers:
'max': true,
// we specify we want the 'top' three numbers:
'howMany': 3
}));
function choseBig(myArray, opts) {
var settings = {
'max': true,
'howMany': 2
};
opts = opts || {};
Object.keys(opts).forEach(function(o) {
settings[o] = opts[o];
});
return myArray.sort((a, b) => settings.max === true ? b - a : a - b).slice(0, settings.howMany);
}
console.log(choseBig([1, 2, 3, 4, 5, 9], {
'max': true,
'howMany': 3
}));
JS Fiddle demo.
References:
Array.prototype.forEach.
Array.prototype.slice().
Array.prototype.sort().
Conditional (Ternary) Operator: statement ? ifTrue : ifFalse
How do you use the ? : (conditional) operator in JavaScript?.
Object.keys().
Why not just sort it (descending order) and take the first two entries
biggest = myArray.sort(function(a,b){return b - a}).slice(0,2);
The answers above are probably better and more compact, but in case you don't want to use sort() this is another option
function choseBig (myArray) {
var result = [], firstBig, secondBig;
// select the biggest value
firstBig = Math.max.apply(Math, myArray);
// find its index
var index = myArray.indexOf(firstBig);
// remove the biggest value from the original array
myArray.splice((index), 1);
secondBig = Math.max.apply(Math, myArray);
// push the results into a new array
result.push(firstBig, secondBig);
return result;
}
console.log(choseBig ([1,2,3,4,5,9]));
A linear solution with Array#reduce without sorting.
var array = [1, 2, 3, 4, 5, 9],
biggest = array.reduce(function (r, a) {
if (a > r[1]) {
return [r[1], a];
}
if (a > r[0]) {
return [a, r[1]];
}
return r;
}, [-Number.MAX_VALUE, -Number.MAX_VALUE]);
document.write('<pre>' + JSON.stringify(biggest, 0, 4) + '</pre>');
Edit: more than one biggest value
var array = [1, 2, 3, 4, 5, 9, 9],
biggest = array.reduce(function (r, a) {
if (a > r[1]) {
return [r[1], a];
}
if (a > r[0]) {
return [a, r[1]];
}
return r;
}, [-Number.MAX_VALUE, -Number.MAX_VALUE]);
document.write('<pre>' + JSON.stringify(biggest, 0, 4) + '</pre>');
With Math#max and Array#splice
var first = Math.max(...arr)
arr.splice(arr.indexOf(first))
var second = Math.max(...arr)
and with ES6 spread operator
I like the linear solution of Nina Scholz. And here's another version.
function chooseBig (myArray) {
var a = myArray[0], b = myArray[0];
for(i = 1; i < myArray.length; i++) {
if (myArray[i] === a) {
continue;
} else if (myArray[i] > a) {
b = a;
a = myArray[i];
} else if (myArray[i] > b || a === b) {
b= myArray[i];
}
}
return [a, b];
}
If you want to retrieve the largest two values from a numeric array in a non-destructive fashion (e.g. not changing the original array) and you want to make it extensible so you can ask for the N largest and have them returned in order, you can do this:
function getLargestN(array, n) {
return array.slice(0).sort(function(a, b) {return b - a;}).slice(0, n);
}
And, here's a working snippet with some test data:
function getLargestN(array, n) {
return array.slice(0).sort(function(a, b) {return b - a;}).slice(0, n);
}
// run test data
var testData = [
[5,1,2,3,4,9], 2,
[1,101,22,202,33,303,44,404], 4,
[9,8,7,6,5], 2
];
for (var i = 0; i < testData.length; i+=2) {
if (i !== 0) {
log('<hr style="width: 50%; margin-left: 0;">');
}
log("input: ", testData[i], " :", testData[i+1]);
log("output: ", getLargestN(testData[i], testData[i+1]));
}
<script src="http://files.the-friend-family.com/log.js"></script>
[1,101,22,202].sort(function(a, b){return b-a})[0]
[1,101,22,202].sort(function(a, b){return b-a})[1]

Categories