Functional JS - forEach function not working - javascript

I am trying to practice functional programming and I started by working with forEach and reduce to count zeroes in a given array (a problem in eloquent JavaScript chapter 5). But I do not understand why my code works in some instances but not others.
The code below doesn't work and leaving out the else statement seems to be the problem. But why is the else statement necessary here and not in some of the cases I post towards the end of the post?
function forEach(arr, action) {
for(var i = 0; i < arr.length; i++) {
action(arr[i]);
}
}
function reduce(cb, start, array) {
forEach(array, function(el) { start = cb(start, el); });
return start;
}
function countZeroes(array) {
function counter(total, element) {
if(element === 0) return total + 1; //excluding else statement seems to be the issue but why?
}
return reduce(counter, 0, array);
}
console.log(countZeroes([0,1,0,2,0,3, 0,4,2,0])); //returns NaN
If I rewrite the function as such with an else statement it works:
function forEach(arr, action) {
for (var i = 0; i < arr.length; i++) {
action(arr[i]);
}
}
function reduce(cb, start, array) {
forEach(array, function(el) {
start = cb(start, el);
});
return start;
}
function countZeroes(arr) {
function counter(total, element) {
if (element === 0) {
return total + 1;
} else {
return total;
}
}
return reduce(counter, 0, arr);
}
console.log(countZeroes([0, 1, 2, 0]));
However, the code does not require the else statement in the cases below, why?
function countTarget(arr, target) {
var counter = 0;
for(var i = 0; i < arr.length; i++) {
if(arr[i] === target) counter++; //no else statement required here
}
return counter;
}
console.log(countTarget([0,1, 0, 0, 2, 3, 0, 3, 0, 0, 0], 0));
function forEach(arr, action) {
for(var i = 0; i < arr.length; i++) {
action(arr[i]);
}
}
function test(a) {
return a === 0;
}
function countTarget(arr, target) {
var counter = 0;
forEach(arr, function(el) {
if (test(el)) counter++;
});
return counter;
}
console.log(countTarget([1, 2, 3, 0, 3, 0], 0));
I would appreciate thoughts.

Whatever gets returned from your counter function is the passed to the next call to counter. Therefore, you need to return something on your counter function. If the condition doesn't get satisfied, return the unaffected total:
function counter(total, element) {
return (element === 0) ? total + 1 : total;
}
Explanation: your counter function increments a "total" value depending on some logic and returns the incremented value. The returned value is then passed to the counter function again. Your counter function has a code path which doesn't return a value (when the if statement doesn't pass). In this case, you're not returning anything... which is similar to returning undefined. If you were to isolate this, it would look kind of like this:
// Let's say your current "total" count is 15
var total = counter(15, 2);
console.log(total); //-> undefined
Since 2 !== 0 - the condition didn't pass and the result of the function is undefined. Now, the next time counter is called, it will look like this:
var total = counter(undefined, 0);
Since 0 === 0, the condition will pass, and it will try to increment the total... which is undefined:
return undefined + 1; //-> NaN
Hope that helps.

function counter(total, element) {
if(element === 0) return total + 1; //excluding else statement seems to be the issue but why?
}
in this function, you are returning some value (total + 1) when the element is 0. so for every other case this function returns undefined as you did not specify any value for else case.
undefined + number = NaN
you can check the returned value from counter like this
forEach(array, function(el) {
console.log(start);
start = cb(start, el);
});
case 1: no else
console.log(countZeroes([0,1,0,2,0,3, 0,4,2,0]));
logged values
0 1 undefined NaN so on
case 2: with else
console.log(countZeroes([0,1,0,2,0,3, 0,4,2,0]));
logged values
0 1 1 2 2 3 so on
EDIT
when you define a function like below
function f_name() {
// calculations
return some_value;
}
javascript implements it as
function f_name() {
// calculations
return some_value;
return undefined; // implied by javascript
}
a function can have multiple return statements and function returns when it encounters a return in the execution flow and the remaining code becomes unreachable.
so, in your case
function counter(total, element) {
if(element === 0) return total + 1; //excluding else statement seems to be the issue but why?
return undefined; // implied
}
when the element value is 0, the function return total + 1 and the remaining code(return undefined;) becomes unrechable but when element is not 0, the function executes return undefined.
Hope this clarifies your doubt.

Related

Invoke a function with is faster than directly invoking?

I tested performance with a script from "Secrets of Javascript Ninja":
function isPrime(number) {
if (number < 2) {
return false;
}
for (let i = 2; i < number; i++) {
if (number % i === 0) {
return false;
}
}
return true;
}
console.time("isPrime");
isPrime(1299827);
console.timeEnd("isPrime");
console.time("isPrime");
isPrime.apply(1299827);
console.timeEnd("isPrime");
And the result is:
isPrime: 8.276ms
isPrime: 0.779ms
Seems that "apply" is faster?
Your comparison is not accurate, because the first parameter passed to apply is the this value of the called function, and the second parameter passed to apply is an array of parameters that function is to be called with. So, your apply is not calling isPrime with any parameters, so no iterations run, because the condition i < number is not fulfilled when i is 2 and number is undefined:
function isPrime(number) {
console.log('calling with ' + number);
if (number < 2) {
return false;
}
for (let i = 2; i < number; i++) {
if (number % i === 0) {
return false;
}
}
return true;
}
console.time("isPrime");
isPrime(1299827);
console.timeEnd("isPrime");
console.time("isPrime");
isPrime.apply(1299827);
console.timeEnd("isPrime");
If you use apply properly and pass in undefined, [1299827], the result is as expected, very similar. You should also use performance.now() for better precision than console at the millisecond level, though for such a quick operation you might not see that might difference anyway:
function isPrime(number){
console.log('calling with ' + number);
if(number < 2) { return false; }
for(let i = 2; i < number; i++) {
if(number % i === 0) { return false; }
}
return true;
}
const t1 = performance.now();
isPrime(1299827);
const t2 = performance.now();
isPrime.apply(undefined, [1299827]);
console.timeEnd("isPrime");
const t3 = performance.now();
console.log(t2 - t1);
console.log(t3 - t2);
The syntax for .apply is
function.apply(thisArg, [argsArray])
the first parameter thisArg refers to the value of 'this' when calling the function, in your case isPrime.apply(1299827) you passed in 1299827 as 'this' but no parameter, so it's really isPrime(), the for loop is not excuted so it's faster
more on .apply here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
You have to read this.
reference: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
This point is Array.prototype.apply(context = this, args = []), so your code is wrong.
Change your code to this.
// incorrect.
isPrime.apply(1299827);
// correct.
isPrime.apply(this, 1299827);

Why doesn't this function exit after the first iteration?

I do understand how the following function works in general. BUT why doesn't it exit after the first iteration (when there is a palindrome)? It checks the first character against the last in the if statement, which is true, what should (in my logic) execute the return statement... Thank you for any help explaining this! :)
function palindrome(str) {
var lowerCaseStr = str.toLowerCase();
for (var i = 0; i < lowerCaseStr.length; i++)
debugger;
if (lowerCaseStr[i] === lowerCaseStr[lowerCaseStr.length - i - 1]){
return true;
}
return false;
}
It doesn't exit after the first iteration but after lowerCaseStr.length iterations because your code is equivalent to the code below
function palindrome(str) {
var lowerCaseStr = str.toLowerCase();
for (var i = 0; i < lowerCaseStr.length; i++){
debugger;
}
if (lowerCaseStr[lowerCaseStr.length] === lowerCaseStr[-1]){
return true;
}
return false;
}
that is, it iterates lowerCaseStr.length; times but the only thing it does for each iterates is call debugger after that it tests to elements in the array that doesn't exists. (Both indices are out of bounds). That results in a comparison of two times undefined undefined === undefined which is always true.
As a side node if you return either true or false depending on a boolean expression then consider using one return statement instead:
return (lowerCaseStr[i] === lowerCaseStr[lowerCaseStr.length - i - 1]);
You need to switch the logic, check for inequality and return false. If you reach the end, return true.
function palindrome(str) {
var lowerCaseStr = str.toLowerCase();
for (var i = 0; i < lowerCaseStr.length; i++) {
debugger;
if (lowerCaseStr[i] !== lowerCaseStr[lowerCaseStr.length - i - 1]) {
return false;
}
}
return true;
}

Function that counts how often it call another function

I have a 'twice' function that return 2 of the argument passed into it. I also have another function 'runTwice' that counts the number of times it called the 'twice' function (the idea being that I want the 'twice' function to only run 'twice' no matter how often it is called via the 'runTwice' function). Can you please help?
Functions are given below:
var count = 1;
function twice(num){
return num*2;
}
function runTwice(func){
if (count<3){
count++;
return func;
} else {
return 'Function cannot run!';
}
}
var myFunc = runTwice(twice)
var output = [];
for (var i = 0; i < 3; i++){
output.push(myFunc(i));
}
console.log(output);
I would like the output to be [0, 2, 'Function cannot run!'].
I can make this work if I count the 'twice' function directly but I am looking to understand why this doesn't work as presented above.
Just for fun I'll make a generic expireAfter(invocable[, times[, message]]) function:
function expireAfter(invocable, times = 2, message = 'Function cannot run!') {
return function expires() {
if (times > 0) {
times--;
return invocable.apply(this, arguments);
}
return message;
}
}
function twice(n) {
return n * 2;
}
var myFunc = expireAfter(twice);
console.log(Array(3)
.fill()
.map((_, index) => myFunc(index))
);
The function runTwice should return another function that will decide whether to call the function func (using Function.prototype.apply) or to return a string message instead:
function twice(num){
return num * 2;
}
function runTwice(func){
var count = 0; // this will be trapped in a closure along with func
return function() { // this is the function that gets called
count++; // it increments its version of the count variable
if(count <= 2) // if count is less than 2
return func.apply(this, arguments); // then it calls the function func with whatever arguments passed into it and return the returned value of that call
return "Not available anymore!"; // otherwise (count > 2), then it returns a string
}
}
var myFunc = runTwice(twice);
for (var i = 0; i < 3; i++){
console.log(myFunc(i));
}
Even better:
You can pass in the number of times allowed as well:
function double(num) {
return num * 2;
}
function triple(num) {
return num * 3;
}
function run(func, times){
var count = 0; // this will be trapped in a closure along with func and times
return function() { // this is the function that gets called
count++; // it increments its version of the count variable
if(count <= times) // if count is less than times
return func.apply(this, arguments); // then it calls the function func with whatever arguments passed into it and return the returned value of that call
return "Not available anymore!"; // otherwise (count > times), then it returns a string
}
}
var double2times = run(double, 2); // double2times can only be called 2 times
var triple5times = run(triple, 5); // triple5times can only be called 5 times
for (var i = 0; i < 10; i++){
console.log("Double:", double2times(i));
console.log("Triple:", triple5times(i));
}

How to ilterate with recursive function (javascript)

function numberSum(num) {
var str = num.toString();
var arrNum = str.split('').map(Number);//arrNum = [1, 2, 3];
//For-looping
var result = 0;
for (var i = 0; i < arrNum.length; i++) {
result = result + arrNum[i];
}
return result;
}
console.log(numberSum(22222)); // 2 + 2 + 2 + 2 + 2 = 10
I did this with For-looping and then iterate it. The question is, how do i did the same but with Recursive Function?
You could use only the first element for addition and call the function with the rest of the array again.
In this case, a check is made for the length, this returns either 0 if the array has no items or the item count, then a shift is made which returns the first item of the array. Additionaly the function is called again with the reduced array.
function iter(array) {
return array.length && array.shift() + iter(array);
// ^^^^^^^^^^^^ exit condition,
// if zero, return zero,
// otherwise return the
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ iteration part
// return the first value and
// call recursion again
}
function numberSum(v) {
function iter(array) {
return array.length && array.shift() + iter(array);
}
return iter(v.toString().split('').map(Number));
}
console.log(numberSum(22222)); // 2 + 2 + 2 + 2 + 2 = 10
For the input you have (22222) your function is a practical solution. If you want a function that takes a number and adds itself together a certain number of times you can simply do this...
function sums(a, b) {
return a * b;
}
sums(2, 5);
//=> 10
But if you really require an example of a recursive function to do this the following will achieve the same result...
var num = 2;
var iterate = 5;
function sums(n, count, total) {
if (count === 0) {
return total;
} else {
return sums(n, --count, total+n);
}
}
console.log(sums(num, iterate, 0));
//=> 10
Hope that helped. :)
(See this blog post on JavaScript recursion by integralist).

RangeError: Maximum call stack size error in JS function

function findRandomDayIndex() {
var dayindex = _.random(0, 39);
var slot = dayslots[dayindex]; // array of 40 objects
if(slot.filled === true || slot === undefined) {
return findRandomDayIndex();
} else {
return dayindex;
}
}
I get the error:
RangeError: Maximum call stack size exceeded iteration of the same function
How can better write the function?
You might try out this version
function findRandomDayIndex()
{
var dayindex = Math.random(0, 39);
while( ( slot = dayslots[dayindex] ) == null )
dayindex = Math.random(0, 39);
return dayindex ;
}
Please check the consistence of dayslot, in order to prevent infinite while-looping anyway
You don't need recursion to do this. With a little refactoring you can map your array to save indexes, then filter undefined and filled values and then get random item from that new array, e.g.:
function findRandomDayIndex() {
var notFilled = dayslots.map(function(value, index) {
return {
value: value,
index: index
};
}).filter(function(day) {
return day.value !== undefined && !day.value.filled;
});
if (!notFilled.length) {
// all items of dayslots are undefined or filled
// handle this case here
}
var dayindex = _.random(0, notFilled.length - 1);
return notFilled[dayindex].index;
}
Here's one that handles when all dayslots are filled. The sample code returns -1, but you can update it to return anything.
function findRandomDayIndex() {
// Create an array of indexes for items that aren't filled
var m = _.map(dayslots, function(v, k) {
if (!v.filled) {
return k;
}
});
// The array will have undefined for those already filled, so we use lodash without to remove them
m = _.without(m, undefined);
if (m.length === 0) {
// Handle when all slots are filled
return -1;
} else {
// return a random index
return m[_.random(0, m.length)];
}
}
// Lets create some test data
var dayslots = [];
for (var i = 0; i < 40; i++) {
dayslots.push({
filled: false
});
}
// Test our function
console.log(findRandomDayIndex());
If you change the filled to true, then you'll get a -1.
As #CodeiSir commented since no slot was free code was going into infinite loop. So I changed the code as below and it works fine. Thank you!
if (_.findWhere(dayslots, {filled: false}) !== undefined) {
var dayindex = _.random(0, 39);
var slot = dayslots[dayindex];
console.log(dayslots.length);
if(slot.filled === true || slot === undefined) {
return findRandomDayIndex();
} else {
return dayindex;
}
}

Categories