And Im getting Maximum call stack size exceeded error when i input large numbers
TJ's answer is great, per usual. I'll include this answer because it addresses the stack-overflow issue. Below, we look at a simplified implementation of oddCount
.
const oddCount = n =>
n === 0
? 0
: (n % 2) + oddCount (n - 1)
console.log (oddCount (0)) // 0
console.log (oddCount (1)) // 1
console.log (oddCount (2)) // 1
console.log (oddCount (3)) // 2
console.log (oddCount (4)) // 2
console.log (oddCount (5)) // 3
console.log (oddCount (15)) // 8
// i can't do it, captain!
console.log (oddCount (100000)) // RangeError: Maximum call stack size exceeded
To fix this, we have to move the recursive call into tail position; meaning all other evaluation of the function is complete and making the recursive call is the last thing your function does...
We can do this in a variety of ways, but one versatile technique involves simply adding a state parameter (or more, if needed) to your function. Here, we add acc
which represents an accumulated sum of the odd numbers and set the default value to 0
.
const oddCount = (n, acc = 0) =>
n === 0
? acc
: oddCount (n - 1, acc + (n % 2))
console.log (oddCount (0)) // 0
console.log (oddCount (1)) // 1
console.log (oddCount (2)) // 1
console.log (oddCount (3)) // 2
console.log (oddCount (4)) // 2
console.log (oddCount (5)) // 3
console.log (oddCount (15)) // 8
// but still broken???
console.log (oddCount (100000)) // RangeError: Maximum call stack size exceeded
Most importantly, notice the position of oddCount
now – all of the arguments to oddCount
can be completely evaluated and only thing left to do is recur oddCount
– we have achieved a tail-recursive form!
// ...
: oddCount (n - 1, acc + (n % 2))
In a language that supports tail-call elimination, that's as far as you'd need to go to avoid your stack error. As of today however, JavaScript has no such optimisation. ES6 standard included support for tail-call elimination, but somewhere along the lines it became politics and the optimisation seems to be stuck in feature limbo for the time being.
No problem though. JavaScript already supports infinite looping with while
, so we can invent our own mechanism to write stack-safe recursive functions even in a language that doesn't have tail call elimination. Cool!
const recur = (...values) =>
({ tag : recur, values : values })
const loop = f =>
{
let acc = f ()
while (acc && acc.tag === recur)
acc = f (...acc.values)
return acc
}
const oddCount = n =>
loop ((m = n, acc = 0) =>
m === 0
? acc
: recur (m - 1, acc + (m % 2)))
// now it works for huge numbers
console.log (oddCount (10000000)) // 5000000