Reputation: 107
I am attempting to teach myself how to write recursive functions and someone suggested trying to convert loops into recursions. So I am trying to change the first for loop function into a recursive function. Here is my code:
// Function that uses for loop.
function onlyOne(value1, value2, value3) {
var array = [value1, value2, value3];
var count = 0;
for(var i = 0; i < array.length; i++) {
if(!!array[i] === true) {
count ++;
}
} if(count === 1) {
return true;
} else {
return false;
}
}
// Function that uses recursion.
function onlyOne2(a, b, c) {
var array = [a, b, c];
var count = 0;
var numTrue = 0;
if(!!array[count] === true) {
numTrue++;
}
if(count === array.length-1) {
if(numTrue === 1) {
return true;
} else {
return false;
}
}else {
count++;
return onlyOne2(a, b, c);
}
}
console.log(onlyOne2(true, false, false));
The purpose of each function is to return true if there is exactly one argument that is truthy. Otherwise the function returns false. The for loop function works properly. However, when I use the recursive function I get the error: Maximum call stack size exceeded. I was wondering what I am doing incorrectly. Thank you for your help!
Upvotes: 3
Views: 4355
Reputation: 74204
You're going about this the wrong way. You're trying to convert the entire function into a recursive function. Instead, as suggested, you only need to convert the loop into a recursive function:
I am attempting to teach myself how to write recursive functions and someone suggested trying to convert loops into recursions.
So, let's start by looking at your original function. I cleaned it up for you:
assert("Test 1", onlyOne(false, 0, "") === false);
assert("Test 2", onlyOne(false, 0, "x") === true);
assert("Test 3", onlyOne(false, 1, "") === true);
assert("Test 4", onlyOne(false, 1, "x") === false);
assert("Test 5", onlyOne(true, 0, "") === true);
assert("Test 6", onlyOne(true, 0, "x") === false);
assert("Test 7", onlyOne(true, 1, "") === false);
assert("Test 8", onlyOne(true, 1, "x") === false);
function onlyOne(a, b, c) {
const array = [a, b, c];
// +-- state of the loop
// | +-- initial value of the state
// | |
// v v
var count = 0;
// +-- loop variant
// | +-- initial value of the loop variant
// | |
// v v
for (let i = 0; i < 3; i++)
if (array[i]) count++;
return count === 1;
}
function assert(test, condition) {
console.log(test, condition ? "passed" : "failed");
}
Notice that I annotated the state of the loop and the loop variant. These will be the arguments of our recursive function. We'll replace the for
loop with a call to the recursive function using the initial values of the state and the loop variant as inputs. The result of the recursive function will be the final state of the loop:
assert("Test 1", onlyOne(false, 0, "") === false);
assert("Test 2", onlyOne(false, 0, "x") === true);
assert("Test 3", onlyOne(false, 1, "") === true);
assert("Test 4", onlyOne(false, 1, "x") === false);
assert("Test 5", onlyOne(true, 0, "") === true);
assert("Test 6", onlyOne(true, 0, "x") === false);
assert("Test 7", onlyOne(true, 1, "") === false);
assert("Test 8", onlyOne(true, 1, "x") === false);
function onlyOne(a, b, c) {
const array = [a, b, c];
const count = loop(0, 0);
return count === 1;
function loop(count, i) {
return i < 3 ?
loop(array[i] ? count + 1 : count, i + 1) :
count; // return final state of the loop
}
}
function assert(test, condition) {
console.log(test, condition ? "passed" : "failed");
}
Hopefully, now you know how to convert any loop into a recursive function.
Upvotes: 0
Reputation: 386560
You could take a counter for the wanted truthy values and rest parameters ...
for the recursive function.
It works with tail recursion and an arbitrary count of arguments.
function only(count, v, ...rest) {
if (v) { // check value
if (!count) { // check count is zero
return false; // because found more truthy than needed
}
count--; // decrement count
}
if (!rest.length) { // check length is zero
return !count; // return not count
}
return only(count, ...rest); // tail call with count and rest
}
console.log(only(1, false, false, false)); // false
console.log(only(1, false, false, true)); // true
console.log(only(1, false, true, false)); // true
console.log(only(1, false, true, true)); // false
console.log(only(1, true, false, false)); // true
console.log(only(1, true, false, true)); // false
console.log(only(1, true, true, false)); // false
console.log(only(1, true, true, true)); // false
console.log('-----');
console.log(only(2, false, false, false)); // false
console.log(only(2, false, false, true)); // false
console.log(only(2, false, true, false)); // false
console.log(only(2, false, true, true)); // true
console.log(only(2, true, false, false)); // false
console.log(only(2, true, false, true)); // true
console.log(only(2, true, true, false)); // true
console.log(only(2, true, true, true)); // false
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 1
Reputation: 135197
Here's another way to solve it – I recently wrote another answer that aims to help learners achieve high-level thinking using recursion. Techniques used here are written about in detail. If you're interested, you can read it here.
const None =
Symbol ()
const loop = ([ x = None, ...xs ], foundTruthy = false) =>
x === None
? foundTruthy
: Boolean (x)
? foundTruthy
? false
: loop (xs, true)
: loop (xs, foundTruthy)
const onlyOne = (...xs) =>
loop (xs, false)
console.log
( onlyOne () // false
, onlyOne (0) // false
, onlyOne (1) // true
, onlyOne (0, '', false, 1) // true
, onlyOne (0, 0, 0, true, 1) // false
)
Note loop
can be made generic so that onlyOne
doesn't need to depend on a specialized helper – mind the stack overflow
const None =
Symbol ()
const always = x => _ =>
x
const recur = (...values) =>
({ recur, values })
const loop = (f = always (null) , acc = f ()) =>
acc && acc.recur === recur
? loop (f, f (...acc.values))
: acc
const onlyOne = (...list) =>
loop (([ x = None, ...xs ] = list, foundTruthy = false) =>
x === None
? foundTruthy
: Boolean (x)
? foundTruthy
? false
: recur (xs, true)
: recur (xs, foundTruthy))
console.log
( onlyOne () // false
, onlyOne (0) // false
, onlyOne (1) // true
, onlyOne (0, '', false, 1) // true
, onlyOne (0, 0, 0, true, 1) // false
)
Nina's answer is a great example of how function parameters can reduce program complexity – this adaptation shows how the same program can be written in a functional expression
const None =
Symbol ()
const only = (count = 1, x = None, ...xs) =>
x === None
? count === 0
: Boolean (x)
? only (count - 1, ...xs)
: only (count, ...xs)
console.log
( only () // false
, '---'
, only (0) // true
, only (0, false) // true
, only (0, true) // false
, '---'
, only (1) // false
, only (1, false) // false
, only (1, false, true) // true
, only (1, false, true, true) // false
, '---'
, only (2, false, true) // false
, only (2, false, true, true) // true
, only (2, false, true, true, true) // false
)
Upvotes: 0
Reputation: 2855
I would recommend starting with a simpler example:
function logRecurse(i, arr) {
if (i < arr.length) {
console.log(arr[i]);
logRecurse(i+1, arr);
}
}
var array = [false, false, true, false];
logRecurse(0, array);
Then add the return statements:
function truthyRecurse(i, arr) {
if (i < arr.length) {
if (arr[i]) { // could be just: return arr[i] || truthyRecurse(...)
return true;
} else {
return truthyRecurse(i+1, arr);
}
}
return false;
}
var array = [false, false, true, false];
console.log(truthyRecurse(0, array));
But the real usefulness of recursion is with something like a directory structure. Have a go at that next! You can put arrays into arrays to simulate it:
function logFiles(dir) {
// log filename (typeof string) or recurse into dir
}
var dir = [
"file1",
"file2",
[ // sub dir
"subfile1",
[ // sub-sub dir
"sub-subfile1",
"sub-subfile2",
"sub-subfile3",
],
"subfile2",
]
];
logFiles(dir);
Upvotes: 0
Reputation: 23955
This is interesting. Ok, so we have a function that returns true if exactly one of the items in the array is truthy. Let's think about the basic requirements in a recursion.
Base case, array is empty:
if (!array.length)
return false;
Base case, there's only one element in the array:
if (array.length == 1)
return !!array[0] === true
Otherwise:
let next = f(rest-of-array)
if (!!array[0] === true)
return !next
else
return next
JavaScript code:
function f(arr, i){
// Same as !array.length
if (i == arr.length)
return false;
// Same as array.length == 1
if (i == arr.length - 1)
return !!arr[i]
// Same as f(rest-of-array)
let next = f(arr, i + 1)
if (!!arr[i])
return !next
return next
}
Output:
f([undefined,2,0], 0)
=> true
f([1,2,undefined], 0)
=> false
f([undefined,false,1], 0)
=> true
f([5,false,false], 0)
=> true
Upvotes: 0
Reputation: 229
You might as well try this. You dont need to change your function structure or anything and it will work with any number of arguments.
function onlyOne(a,b,c){
let arr = Array.from(arguments);
let count = 0;
let item = arr.shift();
if(true === item){
++count;
}
if(arr.length > 0) {
count += onlyOne.apply(this,arr);
return count == 1;
}
return count;
}
Upvotes: 0
Reputation: 48745
You can convert any for
loop to recursion:
let a=0, b=1,
for (let n=5; n > 0; n--) {
const tmpa = a;
a = b;
b += tmpa;
}
Here a
. b
and n
changes and the reason is the a
value which has the 6th fibonacci number. So we make them parameters and return:
function forLoop(n, a, b) {
if(n > 0) {
return forLoop(n - 1, b, a + b);
}
return a;
}
const a = forLoop(5, 0, 1);
So for your function that uses for
loop:
function onlyOne(value1, value2, value3) {
const array = [value1, value2, value3];
let count = 0;
for(var i = 0; i < array.length; i++) {
if(!!array[i] === true) {
count ++;
}
}
if(count === 1) {
return true;
}
return false;
}
The bindings you are changing are count
and i
:
function onlyOne(value1, value2, value3) {
function iterate(count, i) {
if (i < array.length) {
if (!!array[i] === true) {
return iterate(count + 1, i + 1);
}
return iterate(count, i + 1);
}
return count;
}
const array = [value1, value2, value3];
const count = iterate(0, 0);
if(count === 1) {
return true;
}
return false;
}
Since you only have 3 elements this probably is good enough but imagine you wanted to implement something like this for an array you should plan for returning as fast as you know you are done. In a for
version it would look like this:
function onlyOne(arr) {
let count = 0;
const len = array.length; // cache
for (index = 0; index < len; index++) {
if (arr[index]) {
count++;
if(count > 1) return false; // early return
}
}
return count === 1;
}
The same with recursion:
function onlyOne(arr) {
function iterate (index, count) {
if (index < len && count <= 1) {
return iterate(
index + 1,
count + (array[index] ? 1 : 0)
);
}
return count;
}
const len = array.length; // cache
const count = iterate(0, 0);
return count === 1;
}
Notice it's not exactly the same since we are expecting a count from the iteration and return
only returns back to the callee. Thus early return here is just not iterating more values but we still return a count that is the subject of the final result. You could do the same in the for
by using break
instead of return
.
The same goes for any
/some
which should return at the very first true and all
that should return false for the first false value. No point in iterating through every element when it cannot change the answer.
Upvotes: 0
Reputation: 33726
if(!!array[i] === true)
use the boolean value directly: if(array[i])
You're asking for a true
value to return true
if (count === 1) { // <- Use this comparison to return the specific value.
return true;
^
} else {
return false;
^
}
Return the comparison directly: return count === 1;
In your function onlyOne2
your "recursion case" to stop the loop is incorrect.
count === array.length-1
^ ^
You have to use the index i
// Function that uses for loop.
function onlyOne(value1, value2, value3) {
var array = [value1, value2, value3];
var count = 0;
for (var i = 0; i < array.length; i++) {
if (array[i]) {
count++;
}
}
return count === 1;
}
console.log(onlyOne(true, false, false));
function onlyOne2(value1, value2, value3, count, i) {
var array = [value1, value2, value3];
if (i === array.length) return count === 1;
if (array[i]) count++;
return onlyOne2(value1, value2, value3, count, ++i);
}
console.log(onlyOne2(true, false, false, 0, 0));
See? now is working the loop using recursion.
Upvotes: 1
Reputation: 168
The problem with above code is that every time the function recurses count
and numTrue
get declared again and hence an infinite loop occurs, Here is the correct code(declaring count
and numTrue
as global:
var count = 0;
var numTrue = 0;
function onlyOne2(a, b, c) {
var array = [a, b, c];
if(!!array[count] === true) {
numTrue++;
}
if(count === array.length-1) {
if(numTrue === 1) {
return true;
} else {
return false;
}
}else {
count++;
return onlyOne2(a, b, c);
}
}
console.log(onlyOne2(true, false, false));
Upvotes: 0