I'm writing a function and this is the last piece of the puzzle, which I thought would be easy to deal with, but after some unsuccessful tinkering with for and while loops, I tried looking it up online and still couldn't find an answer. I came across some obscure and complex solutions, but I think there should be a more straightforward way to solve this. For example, if I have an array
[1, 2, 3, 5, 7, 9]
And the argument is 10, the function should return 6 (1 + 2 + 3), because the sum of the values should be <= 10 (or whatever number is passed). If the argument is 4, the function should return 3 (1 + 2) and so on.
You can use a for loop:
const arg = 10
const arr = [1, 2, 3, 5, 7, 9]
let res = 0;
const calc = (arr, limit) => {
for (num of arr) {
if (num + res > limit) {
break;
}
res += num;
}
return res;
}
console.log(calc(arr, arg))
.reduce() each current value (cur) added to the accumulator (acc) and checked vs. the limit (max). If acc + cur is less than limit then return acc+ cur, otherwise return acc. Added .sort() if the array happens to be out of order, as per Spectric's comment.
const array = [1,2,3,5,6,8];
const mixed = [0,7,3,8,2,1];
const A = 10;
const B = 15;
const closest = (arr, max) => {
return arr.sort((a, b) => a - b).reduce((acc, cur) =>
(acc + cur) > max ? acc : (acc + cur));
}
console.log(closest(array, A));
console.log(closest(array, B));
console.log(closest(mixed, B));
One interesting approach is to realize that we want to do a fold (something like Array.prototype.reduce) but one which we can escape early. The answer from zer00ne does this by choosing to check the condition on every iteration, and just continually returning the accumulator each time the condition is not met. That's fine for many use-cases, but it would be nice to make it more explicit.
I know of two ways to do this. One is to have some signal value that we would return -- probably a symbol -- to say, "We're done, just return the accumulator." The downside is that this function now depends on that external signal value. It's not terrible, and often might be the right solution. It's not hard to write, and I'll leave it as an exercise.
The other technique is to require the callback to explicitly choose whether to continue the iteration or stop by supplying it with next and done functions. If we want to continue, we call next with the next accumulator value. If we're finished, we just call done. Here's a version of that:
const fold = (fn) => (a) => ([x, ...xs]) =>
x == undefined ? a : fn (a, x, (r) => fold (fn) (r) (xs), () => a)
const sumUpTo = (n) =>
fold ((a, x, next, done) => a + x > n ? done () : next (a + x)) (0)
console .log (sumUpTo (10) ([1, 2, 3, 5, 7, 9])) //=> 6
console .log (sumUpTo (4) ([1, 2, 3, 5, 7, 9])) //=> 3
console .log (sumUpTo (10) ([1, 2, 3, 4, 5, 6])) //=> 10
sumUpTo takes our total value and returns a function that takes a list of numbers. It does this by calling fold using a callback function, the initial value (0 for a sum), and eventually passing our list of numbers. That callback does the work we care about. Then fold repeatedly calls it until it runs out of values or done is called.
We can break down the one-liner version above to focus specifically on the callback, if it makes it more clear:
const callback = (n) => (a, x, next, done) =>
a + x > n
? done ()
: next (a + x)
const sumUpTo = (n) => fold (callback (n)) (0)
It's a very elegant pattern, to my mind.
Quickest way with smallest code footprint:
const dataset = [1, 2, 3, 5, 7, 9]
const getMaxSum = (dataset, max) => {
var sum = 0, num;
for( num of dataset ) {
if( sum + num > max ) break // very important to break once satisfied
sum += num
}
return sum
}
console.log( getMaxSum(dataset, 10) )
console.log( getMaxSum(dataset, 20) )
Related
In the game idle heroes, demon bells can be level 1,2,3,4. A level 4 is made of 4 level 1, a level 3 is made of 3 level 1 and so on.
I want to find all arrangements of db given a fixed number. I made this recursive algorithm in javascript:
Closer with a more simplified approach:
function findDB(numDB, arr) {
console.log("findDB"+numDB);
if (numDB == 1) {
console.log("HERE2");
return 1;
} else {
for (let i = 1; i < numDB; i++) {
console.log("FOR"+i);
console.log("COND" +(numDB + (numDB-i)));
if((numDB + (numDB-i)) > numDB+1)
continue;
arr= arr.concat([numDB,[i,findDB(numDB - i, arr)]]);
}
return arr;
}
}
var final = []
var y = findDB(3, final);
console.log(JSON.stringify(y));
Output:
findDB(2) CORRECT!
findDB2
FOR1
COND3
findDB1
HERE2
[2,[1,1]]
FindDB(3) is missing 1,1,1,
findDB3
FOR1
COND5
FOR2
COND4
findDB1
HERE2
[3,[2,1]]
here is intended output for input 1 through 6 (algo needs to scale for any number input)
/1/ (1)
/2/ (2),
(1,1)
/3/ (3),
(2,1),
(1,1,1)
/4/ (4),
(3,1),
(2,2),(2,1,1),
(1,1,1,1)
/5/ (4,1),
(3,2),(3,1,1),
(2,2,1),(2,1,1,1),
(1,1,1,1,1)
/6/ (4,2),(4,1,1),
(3,3),(3,2,1),(3,1,1,1),
(2,2,2),(2,2,1,1),(2,1,1,1,1)
(1,1,1,1,1,1)
This is called the partitions of a number, and is a well-known problem. I'm sure computer scientists have more efficient algorithms than this, but a naive recursive version might look like this:
const partitions = (n, m = n) =>
m > n
? partitions (n, n)
: m == 1
? [Array (n) .fill (1)]
: m < 1
? [[]]
: [
... partitions (n - m, m) .map (p => [m, ...p]),
... partitions (n, m - 1)
];
[1, 2, 3, 4, 5, 6] .forEach ((n) => console .log (`${n}: ${JSON .stringify (partitions (n))}`))
And if you're worried about the default parameter (there sometimes are good reasons to worry), then you can just make this a helper function and wrap it in a public function like this:
const _partitions = (n, m) =>
m > n
? _partitions (n, n)
: m == 1
? [Array (n) .fill (1)]
: m < 1
? [[]]
: [
... _partitions (n - m, m) .map (p => [m, ...p]),
... _partitions (n, m - 1)
];
const partitions = (n) => _partitions (n, n);
[1, 2, 3, 4, 5, 6] .forEach ((n) => console .log (`${n}: ${JSON .stringify (partitions (n))}`))
in either case, n is the integer we're summing to, and m is the maximum integer we can use. If m is too large, we simply call again with an appropriate m. If it equals 1, then we can only have an array of n 1's. If m reaches zero, then we have only the empty partition. Finally, we have two recursive cases to combine: When we choose to use that maximum number, we recur with the remainder and that maximum, prepending the maximum to each result. And when we don't use the maximum, we recur with the same target value and a decremented maximum.
I feel as though this has too many cases, but I don't see immediately how to combine them.
The time is exponential, and will be in any case, because the result is exponential in the size of n. If we added memoization, we could really speed this up, but I leave that as an exercise.
Update
I was bothered by those extra cases, and found an Erlang answer that showed a simpler version. Converted to JS, it might look like this:
const countdown = (n) => n > 0 ? [n , ...countdown (n - 1)] : []
const _partitions = (n, m) =>
n < 0
? []
: n == 0
? [[]]
: countdown (m) .flatMap (x => _partitions (n - x, x) .map (p => [x, ...p]))
We have a quick helper, countdown to turn, say 5 into [5, 4, 3, 2, 1]. The main function has two base cases, an empty result if n is negative and a result containing only the empty partition if n is zero. Otherwise, we countdown the possibilities for the maximum value in a single partition, and recur on the partitions for the target less than this new maximum, adding the maximum value to the front of each.
This should have similar performance characteristics as the above, but it somewhat simpler.
Here is a recursive function that produces the results you want. It attempts to break down the input (numDB) into parts up to the maximum number (maxDB, which defaults to 4). It does this by taking the numbers from numDB down to 1 and adding all the possible results from a recursive call to that number, noting that the value of maxDB has to change to be no more than the first number.
const findDB = function(numDB, maxDB = 4) {
if (numDB == 0) return [ [] ];
let result = [];
let thisDB = Math.min(numDB, maxDB);
for (let i = thisDB; i > 0; i--) {
findDB(numDB - i, Math.min(i, thisDB)).forEach(n => {
result.push([i].concat(n));
});
}
return result;
}
;
[6, 5, 4, 3, 2, 1].forEach((i) => console.log(JSON.stringify(findDB(i))))
.as-console-wrapper {
min-height: 100% !important;
}
I've written the above function in the style in your question, with the use of various ES6 Array methods it can be simplified:
const DBlist = (n) => [...Array(n).keys()].map(k => n - k)
const findDB = (numDB, maxDB = 4) => {
if (numDB == 0) return [ [] ];
const thisDB = Math.min(numDB, maxDB);
return DBlist(thisDB).flatMap((i) => findDB(numDB - i, Math.min(i, thisDB)).map(a => [i, ...a]))
}
DBlist(6).forEach((n) => console.log(JSON.stringify(findDB(n))))
.as-console-wrapper {
min-height: 100% !important;
}
In learning about recursion, I've come across this problem and I've solved it, but not in the way I would like. In my solution, I'm storing values within an array declared in global scope.
Here's my code:
//I don't want array in global scope, but within the function
let array = [];
const rangeOfNumbers = (startNum, endNum) => {
if (startNum === endNum) {
array.splice(0, 0, startNum);
return array;
}
{
array.splice(0, 0, endNum);
// console.log(array)
const newEndNum = endNum - 1;
endNum = newEndNum;
return rangeOfNumbers(startNum, endNum);
}
}
console.log(rangeOfNumbers(6, 10))
I've tried including it as an argument const rangeOfNumbers = (startNum, endNum, array = []) but that leads to the recreation of an array every time the function is called, so when I call the function recursively, so the returned value is an array of a single value [6]. The same (undesired) behavior is observed when I declare a mutable variable within the function scope
const rangeOfNumbers = (startNum, endNum) => {
let array = [];
if(startNum === endNum) {...
As far as I understand, the splice method should not be an issue here, since it isn't deleting any elements.
In general, I'm having trouble understanding how to formulate the easiest base case whenever I try to solve problems recursively. So I have a feeling that - even though I think the base logic is screwing me.
Points to anyone who can offer hints on how to think of this more clearly, rather than simply offering the answer.
Thanks!
Your question asks how you can use parameters "as storage" and this example hopes to show such a technique -
if a is greater than b, the base case has been reached. return the result, r
(inductive) a is less than or equal to b. Append a to the result and recur on the sub-problem (a+1, b)
const range = (a, b, r = []) =>
a > b
? r // # 1
: range(a + 1, b, [...r, a]) // # 2
console.log(JSON.stringify(range(0,7))) // [0,1,2,3,4,5,6,7]
console.log(JSON.stringify(range(3,9))) // [3,4,5,6,7,8,9]
console.log(JSON.stringify(range(9,3))) // []
If you prefer the more verbose variable names -
const range = (startNum, endNum, result = []) =>
startNum > endNum
? result
: range(startNum + 1, endNum, [...result, startNum])
And here's a variant of range that allows you to build ranges in either direction -
if a is greater than b, compute the reversed range, (b,a) and .reverse the result
(inductive) a is less than or equal to b. If a is less than b, append a to the result and recur on the sub-problem (a+1, b)
(inductive) a is equal to b. The base case has been met, append a to the result and return
const range = (a, b, r = []) =>
a > b
? range(b, a).reverse() // #1
: a < b
? range(a + 1, b, [...r, a]) // #2
: [...r, a] // #3
console.log(JSON.stringify(range(0,7))) // [0,1,2,3,4,5,6,7]
console.log(JSON.stringify(range(3,9))) // [3,4,5,6,7,8,9]
console.log(JSON.stringify(range(9,3))) // [9,8,7,6,5,4,3]
console.log(JSON.stringify(range(-3,-7))) // [-3,-4,-5,-6,-7]
console.log(JSON.stringify(range(-7,-3))) // [-7,-6,-5,-4,-3]
console.log(JSON.stringify(range(2,-2))) // [2,1,0,-1,-2]
console.log(JSON.stringify(range(-2,2))) // [-2,-1,0,1,2]
Check if the startNum and endNum are equal. If they are return [startNum]. If not, return [startNum] and concat to it the result of calling rangeOfNumbers with startNum incremented by 1:
const rangeOfNumbers = (startNum, endNum) =>
startNum === endNum ?
[startNum]
:
[startNum].concat(rangeOfNumbers(startNum + 1, endNum));
console.log(rangeOfNumbers(6, 10))
Another non functional approach (the arr is mutated) is to create a function that holds the array, and run the recursion inside it:
const rangeOfNumbers = (startNum, endNum) => {
const arr = [];
const fn = (startNum, endNum) => {
arr.push(startNum);
if(startNum !== endNum) fn(startNum + 1, endNum);
};
fn(startNum, endNum);
return arr;
}
console.log(rangeOfNumbers(6, 10))
This is a bi-directional approach based on #ThankYou's solution. The main difference is that it uses (b - a) / Math.abs(b - a) to calculate the step - 1 / -1.
const range = (a, b, r = []) =>
a === b
? [...r, a]
: range(a + (b - a) / Math.abs(b - a), b, [...r, a])
console.log(JSON.stringify(range(0,7))) // [0,1,2,3,4,5,6,7]
console.log(JSON.stringify(range(3,9))) // [3,4,5,6,7,8,9]
console.log(JSON.stringify(range(9,3))) // [9,8,7,6,5,4,3]
console.log(JSON.stringify(range(-3,-7))) // [-3,-4,-5,-6,-7]
console.log(JSON.stringify(range(-7,-3))) // [-7,-6,-5,-4,-3]
console.log(JSON.stringify(range(2,-2))) // [2,1,0,-1,-2]
console.log(JSON.stringify(range(-2,2))) // [-2,-1,0,1,2]
This is largely the same answer as the one from Ori Drori -- the first, functional one, not the barbaric imperative one :-) -- but with a simpler base case, with the spread operator in place of concat, and by counting down end rather than counting up start. I guess that, at that point, it's really not the same function at all, but it still feels very similar in spirit.
const rangeOfNumbers = (start, end) =>
start > end
? []
: [... rangeOfNumbers (start, end - 1), end]
console .log (rangeOfNumbers (3, 12))
The point here is that recursive approaches are often much simpler than imperative ones. If we know that for any shorter range our function will return what it's supposed to, then our recursive case is often very simple. So here, if we know that rangeOfNumbers(3, 11) will yield [3, 4, 5, 6, 7, 8, 9, 10, 11], then we can easily solve our current case, by tacking 12 onto the end. Then with a little thought about base cases, we can easily stitch together our function.
This moves the array to inside the function, you just have to declare an empty one when calling it for the first time.
const rangeOfNumbers = (startNum, endNum, array) => {
if (startNum === endNum) {
array.splice(0, 0, startNum);
return array;
}
array.splice(0, 0, endNum);
// console.log(array)
const newEndNum = endNum - 1;
endNum = newEndNum;
return rangeOfNumbers(startNum, endNum, array);
}
console.log(rangeOfNumbers(6, 10, []));
I have an array of numbers e.g. [2, 4, 5] and must get the factorials in a new array. E.g. [2, 24, 120]
I am using .map as you can see to perform the function on each integer in the array however, this does not work? I assume something is wrong with the recursive function?
Thanks.
function getFactorials(nums) {
if(nums > 1){
factarr = nums.map(x => x * (nums - 1));
}
return factarr;
}
You could take a function for the factorial and map the values.
The function has a recusive style with a check for the value. if the value is zero, then the function exits with one. Otherwise it returnd the product of the actual value and the result of the call of the function with decremented value.
The check uses an implict casting of a falsy to a boolean value for the conditional (ternary) operator ?:.
const fact = n => n ? n * fact(n - 1) : 1;
var array = [2, 4, 5],
result = array.map(fact);
console.log(result);
Just create a function that calculates the factorial of a given number then just use it as the callback for the map function.
As the main part is the way to calculate the factoriel, here's two manners to do that task
Factoriel function iterative way :
const fact = n => {
let f = 1,
i = n;
for(; i > 1; i--) f *= i;
return f;
};
console.log(fact(4)); /** outpuut: 24 **/
Factoriel function recursive way :
const fact = n => n > 1 ? n * fact(n - 1) : n;
console.log(fact(4)); /** outpuut: 24 **/
And the final code :
const arr = [2, 4, 5],
fact = n => n > 1 ? n * fact(n - 1) : n,
factArr = arr.map(i => fact(i));
console.log(factArr); /** output: [2, 24, 120] **/
You are doing the recursion wrong, my approach would be to define the factorial calculator as a separate function:
function getFactorials(nums) {
function factorial(n){
return n === 0 ? 1 : n * factorial(n - 1);
}
return nums.map(x => factorial(x));
}
console.log(getFactorials([0, 1, 2, 3, 4, 5 ,6]));
You need to calculate each factorial in array, your current code is not doing that. Consider following example:
function factorial(num) {
if (num === 0 || num === 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
const facts = [2, 4, 5];
const factsArr = facts.map(num => factorial(num));
In you code you was just multiplying each member of array by array itself.
Instead of doing it recursively, I took the solution from #Amin Jafari which uses reduce(). This function is quicker than the recursive solution.
First we generate an array. We do so by using Array(n + 1). n is the factorial so e.g. for 6! our n would be 6. We get the indices with keys() but Array() by itself only returns a truly empty array and keys() only returns an iterator. So we spread it and put the result of that into a new array. Thus, we have e.g. [0,1,2,3,4,5,6] (for n + 1 with n = 6). We exclude the 0 with slice(1).
Afterwards we finally apply reduce. Reduce iterates over all elements, applies a function while keeping track of an accumulator. Our accumulator here is the current product. So what happens is that 1 * 2 gets calculated and the result saved in a, our accumulator. Then we multiply a with the next value, so 2 * 2* and this happens until we went through our whole self generated array.
This function based on reduce we can then use to transform each value in our original array with map().
const factorial = n => [...Array(n+1).keys()].slice(1).reduce((a,c) => a * c),
data = [2, 4, 5];
let res = data.map(v => factorial(v));
console.log(res);
I know that this post has a lot of answers already, but I might have something to add:
If you are planning to use the recursive function a lot to calculate factorials, let's say you need this in a browser game every .5 secs, you will notice that it is consuming a lot of resources, mainly your time 😉.
My proposition is:
calculate the factorials once
store them in the app state
look them up instead of calculating them
example code (based on Nina Scholz's anwer):
// create this earlier, put it in the application state
const state = {lookupFact: []}
const fact = n => n ? n * fact(n - 1) : 1;
// only calculate the factorials once
function lookupFact(par) {
if (state.lookupFact.length == 0) {
for (var i = 0; i <= 170; i++) {
state.lookupFact[i] = fact(i)
}
}
return state.lookupFact[par]
}
// use it like this later on
console.log(lookupFact(1), lookupFact(10), lookupFact(5))
As I said, you should use this only if you have to calculate factorials all the time.
var arr = [2,3,4,5,6,7]
arr.map((value, ind) => {
var facts = 1;
for (value ;value > 0 ;value--) {
facts = facts *value;
}
console.log(facts);
})
I've written a quick sort function like this:
const quickSort = list => {
if (list.length === 0) return list
const [pivot, ...rest] = list
const smaller = []
const bigger = []
for (x of rest) {
x < pivot ? smaller.push(x) : bigger.push(x)
}
return [...quickSort(smaller), pivot, ...quickSort(bigger)]
}
I want to pass this function to a trampoline function to make it more efficient. However, to make a recursion function compatible with trampoline, I have to return a function that calls the outer function. As is shown below:
const trampoline = fn => (...args) => {
let result = fn(...args)
while (typeof result === "function") {
result = result()
}
return result
}
const sumBelowRec = (number, sum = 0) => (
number === 0
? sum
: () => sumBelowRec(number - 1, sum + number)
)
const sumBelow = trampoline(sumBelowRec)
sumBelow(100000)
// returns 5000050000
How can I transform my quickSort function to make it leverage the trampoline function?
To utilize a trampoline, your recursive function must be tail recursive. Your quickSort function is not tail recursive because recursive calls to quickSort do not appear in tail position, ie
return [...quickSort(smaller), pivot, ...quickSort(bigger)]
Maybe it's hard to see in your program, but the tail call in your program is an array concat operation. If you were to write it without using ES6 syntaxes, we could see this more readily
const a = quickSort(smaller)
const b = quickSort(bigger)
const res1 = a.concat(pivot)
const res2 = res1.concat(b) // <-- last operation is a concat
return res2
In order to make quickSort tail recursive, we can express our program using continuation-passing style. Transforming our program is simple: we do this by adding a parameter to our function and using it to specify how the computation should continue. The default continuation is the identity function which simply passes its input to output – changes in bold
const identity = x =>
x
const quickSort = (list, cont = identity) => {
if (list.length === 0)
return cont(list)
const [pivot, ...rest] = list
const smaller = []
const bigger = []
for (const x of rest) { // don't forget const keyword for x here
x < pivot ? smaller.push(x) : bigger.push(x)
}
return quickSort (smaller, a =>
quickSort (bigger, b =>
cont ([...a, pivot, ...b])))
}
Now we can see that quickSort always appears in tail position. However, if we call our function with a large input, direct recursion will cause many call frames to accumulate and eventually overflow the stack. To prevent this from happening, we bounce each tail call on a trampoline
const quickSort = (list, cont) => {
if (list.length === 0)
return bounce (cont, list);
const [pivot, ...rest] = list
const smaller = []
const bigger = []
for (const x of rest) {
x < pivot ? smaller.push(x) : bigger.push(x)
}
return bounce (quickSort, smaller, a =>
bounce (quickSort, larger, b =>
bounce (cont, [...a, pivot, ...b])))
}
Now we need a trampoline
const bounce = (f, ...args) =>
({ tag: bounce, f, args })
const trampoline = t =>
{ while (t && t.tag === bounce)
t = t.f (...t.args)
return t
}
Sure enough it works
console.log (trampoline (quickSort ([ 6, 3, 4, 8, 1, 6, 2, 9, 5, 0, 7 ])))
// [ 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 ]
We verify that it works with big data. A million numbers between zero and one million...
const rand = () =>
Math.random () * 1e6 >> 0
const big =
Array.from (Array (1e6), rand)
console.time ('1 million numbers')
console.log (trampoline (quickSort (big)))
console.timeEnd ('1 million numbers')
// [ 1, 1, 2, 4, 5, 5, 6, 6, 6, 7, ... 999990 more items ]
// 1 million numbers: 2213 ms
In another question I answered recently, I show the conversion of two other common functions into continuation-passing style.
Stack-safe recursion is something I've covered broadly, with almost 30 answers on the topic
I can not see how trampoline would make your quickSort more efficient. The least thing you will add is the hard string based check if you are in the state of returning, having a value, or in the state of further dividing the result, having a function. This will add quite a bit to the computation time.
What you can do is make it more generic. In general I would say trampoline is a good way to explain what recursion is about, but never good in terms of efficiency compared to the direct function call.
Further, to leverage trampoline, you must create a function that either returns a function or a value. But you need s.th. that can return a divide (there are two recursive sub-calls at quickSort). That is way you need to reuse trampoline (kind of recursion inside your recursion, 2nd level recursion you might want to call it).
const qs = (list) => {
if (list.length === 0)
return list;
const [pivot, ...rest] = list;
const smaller = [];
const bigger = [];
for (let x of rest) {
x < pivot ? smaller.push(x) : bigger.push(x);
}
return [...trampoline(qs)(smaller), pivot, ...trampoline(qs)(bigger)];
};
const trampoline = fn => (...args) => {
let result = fn(...args);
while (typeof result === "function") {
result = result();
}
return result;
};
console.log(trampoline(qs)([1, 6, 2, 4]));
console.log(trampoline(qs)([4, 5, 6, 1, 3, 2]));
I checked with Chromium and this code is actually really working and even sorting.
Did I already mention: this can not be faster then the original direct recursive calls. This will stack up a lot of function objects.
What i'm trying to do is make a recursive version of find, which takes an array and a test function and returns first element of an array, that passes the test. Lets look at the example:
function isEven(num) { return(num%2 == 0); }
var arr = [1, 3, 5, 4, 2];
function findRecursive(arr, func) {
var p = arr.shift();
if (func(p) == true)
return p;
else
findRecursive(arr, func);
}
findRecursive(arr, isEven);
By some reason i'm getting undefined. But if i change shift to pop on line 5, it correctly returns 2. What causes th problem?
You need to return the value of the recursive call, otherwise you return undefined, the standard return value of a function in Javascript.
} else {
return findRecursive(arr, func);
// ^^^
}
You may insert a check for the length of the array, if there is no more element to check. Then you could return undefined intentionally.
function isEven(num) { return num % 2 === 0; }
function findRight(array, fn) {
if (!array.length) {
return;
}
var p = array.pop();
return fn(p) ? p : findRight(array, fn);
}
console.log(findRight([1, 3, 5, 4, 2], isEven)); // 2
console.log(findRight([1, 3, 5], isEven)); // undefined
Recursion is a looping mechanism that was born in the context of functional programming; taking it out of that context only permits a crude understanding of how recursion is meant to be used
Recursion, when used with other functional programming practices like persistent (immutable) data types and pure functions, can be expressed beautifully as a pure expression
const find = (f, [x,...xs]) =>
x === undefined
? x
: f (x)
? x
: find (f, xs)
const isEven = x =>
(x & 1) === 0
console.log (find (isEven, [1, 3, 5, 4, 2])) // 4
console.log (find (isEven, [1, 3, 5, 7, 9])) // undefined
Be careful with recursion in JavaScript, tho – use a stack-safe looping mechanism to avoid blowing the stack on a large array
const recur = (...values) =>
({ type: recur, values })
const loop = f =>
{
let acc = f ()
while (acc && acc.type === recur)
acc = f (...acc.values)
return acc
}
const find = (f, xs) =>
loop ((i = 0) =>
i === xs.length
? undefined
: f (xs [i])
? xs [i]
: recur (i + 1))
const isEven = x =>
(x & 1) === 0
// [ 1, 2, 3, 4, ... 20000 ]
const numbers =
Array.from (Array (2e4), (x,n) => n + 1)
console.log (find (isEven, numbers)) // 2
// this would have blown the stack using the first version of `find`
// but it works just fine here, thanks to loop/recur
console.log (find (x => x < 0, numbers)) // undefined