Today I Learned - Rocky Kev

TIL Avoiding Off-by-one errors and state using Recusion

POSTED ON:

TAGS:

What is a off by one error? More details in this post
What is state? More details in this post

Recursion

Recursion is a function that calls itself.


(Via Freecodecamp)[https://www.freecodecamp.org/news/what-is-recursion-in-javascript/]

Code Snippets

Using these helpers:

// helpers
const first = xs => xs[0]
const rest = xs => xs.slice(1)

SUM:

Adding array values together

const sum = xs =>
xs.length === 0
? 0
: first(xs) + sum(rest(xs));

console.log( sum([1, 2, 3, 4, 5]) ); // 15

This is a different version using tail recursion.

It turns out we can abstract all of the details of traversing a list. The idea is simple. As we walk across a list, we store an accumulator, a value that stores some data that is important to us. For example, if we want to walk across a list of integers and sum them, we could store the current sum in the accumulator. We start with the accumulator set to 0. As we come across each new element, we add the element to the accumulator. When we reach the end, we return the value stored in the accumulator.

const tailSum = list => {
  const go = (acc, xs) =>
    xs.length === 0
      ? acc
      : go(acc + first(xs), rest(xs));
  return go(0, list)
}

console.log( tailSum([1, 2, 3, 4, 5]) ); // 15

A function that returns the value of its recursive call is said to be tail recursive. The advantage is that a tail recursive function can be compiled into a for loop. We will see exactly why later in the course, but for now just notice the following difference between the sum and sum' functions above. In the first sum function, after the recursive call returned its value, we add x to it. In the tail recursive sum', after the recursive call, we immediately return the value returned by the recursion. So in the second case, the compiler can change this recursive call into a simple goto statement. The result is that tail recursive functions tend to run faster than their standard counterparts.

This is via [cornell.edu's cs312 class](More info about Tail Recusion. The JS code is from the YDNL repo

REVERSE:

Flipping array values the other way

const reverse = xs =>
xs.length === 0
? []
: reverse(rest(xs)).concat(first(xs));

console.log( reverse([1, 2, 3, 4, 5]) ); // 5, 4, 3, 2, 1

Oddly enough, you can just do this with the built-in reverse method.🤷

console.log( [1, 2, 3, 4, 5].reverse() ); // 5, 4, 3, 2, 1

REDUCE:
Note: I couldn't get either reduce to work within the web environment.

Via the repo notes:
NOTE: Since tail call optimization is currently only supported by Safari, tail recursion may cause stack overflow in most other JavaScript environments. While others, such as the Chrome devs, appear to be discussing the subject on-and-off, you may wish to, in this case, use a loop here to compromise (and this is an example of balancing the triangle):

const reduce = (f, acc, xs) =>
xs.length === 0
? acc
: reduce(f, f(acc, first(xs)), rest(xs));


// tail recusion reduce
const tailReduce = function(reduceFn, accumulator, iterable){
for (let i of iterable){
accumulator = reduceFn(accumulator, i)
}
return accumulator
}

Via:
https://github.com/you-dont-need/You-Dont-Need-Loops#recursion


Related TILs

Tagged:

TIL math module in SASS!

math in Sass, oh my!

TIL the Quake 3 Fast Inverse Square Root hack

Quake III Arena, a first-person shooter video game, was released in 1999 by id Software and used the algorithm.

TIL that the max size of a Map/Set is 26843544

JS Maps and Sets are implemented by OrderedHashTable. OrderedHashTables double in size when they need to grow, and their max size is 26843544. After 16777216 elements, doubling that exceeds the max, so the allocation fails.Currently these data structures are not implemented to be usable for large in-memory datasets. Also note that the JS specification itself doesn't require that Maps and Sets must scale with available memory.