Why does javascript's implementation of reduce skip execution for the first iteration?
[1,2,3].reduce((acc, val) => {
console.log('acc',acc);
console.log('val',val)
return acc + val;
});
// acc 1
// val 2
// acc 3
// val 3
// 6
I notice that the first statement execution never runs (in this case, I would have expected there to be 6 console logs, 2 for each element). This was very unexpected behavior when I was trying to execute a function with a side effect within each iteration with reduce.
In other languages that I've used, every iteration of the list passed executes. Are there examples otherwise?
Why does this happen and why is the implementation of javascript's native Array reduce like this?
========================= EDIT 1/Solution ========================
To make sure it goes through the first iteration, give it an initial value (the 2nd argument here/ 0 in this case)
[1,2,3].reduce((acc, val) => {
console.log('acc',acc);
console.log('val',val)
return acc + val;
}, 0);
That is because of the fact that on every iteration, the first value is treated as a return value (or the accumulator).
Straight from here, you can see
The accumulator accumulates the callback's return values; it is the
accumulated value previously returned in the last invocation of the
callback, or initialValue, if supplied (see below).
If we look at the source code here, we can see how it's implemented:
Array.prototype.myReduce = function(callback, initialVal) {
var accumulator = (initialVal === undefined) ? undefined : initialVal;
for (var i = 0; i < this.length; i++) {
if (accumulator !== undefined)
accumulator = callback.call(undefined, accumulator, this[i], i, this);
else
accumulator = this[i];
}
return accumulator;
};
In the else structure, we can see that if the value is undefined, we set it to the i-th subindex in the array; which, for the first iteration, is the first one. After that, it becomes the callback (return) value of the iterations which follow.
If you want, you can backtrack and check the output.
Related
I need to find the sum of integers in an array using recursion.
The following code throws an error :
var sum = function(array) {
if(array.length === 0) return 0;
while(array.length) {
sum = array[0] + sum(array.slice(1));
return sum;
}
}
while this works :
var sum = function(array) {
if(array.length === 0) return 0;
while(array.length) {
return array[0] + sum(array.slice(1));
}
}
The difference is the way the sum is returned. Can someone explain?
As mentioned in comments, the while loop never loops - it executes a return statement before testing the loop condition a second time.
Even so, why does the first version work the first time it is called but not the second?
sum = array[0] + sum(array.slice(1));
stores the right hand expression value in sum, after the function call to sum returns. That variable is in scope, so the assignment is valid.
But the assignment doesn't occur until after the call to sum (on the right hand side) returns. Each time sum returns, assignment overwrites the variable sum with the partial, and eventually the final result.
So the first call works successfully, but leaves sum set to the result of the first call. Since you can't call a number, it errors the second time you call it.
Given an object and a key, getElementsThatEqual10AtProperty returns an array containing all the elements of the array located at the given key that are equal to ten.
I want to assign what my .reduce method outputs to a variable then use that variable inside my if statement. My code evaluates to NaN.
function getElementsThatEqual10AtProperty(obj, key) {
var arrayTotal = 0;
obj[key].reduce(function(a,b){
arrayTotal = a + b;
});
if(arrayTotal === 10) {
return obj[key];
}
};
You're approaching reduce incorrectly. You're almost using it like Array#forEach. Remember that callbacks are functions. They expect to return an actual value.
When you read the docs, you'll see that reduce also needs an accumulator, that is an argument after the callback.
const sum = obj[key].reduce(function(a,b){
return a + b
}, 0)
Or if you want the newer and prettier version:
const sum = obj[key].reduce((a,b) => a + b, 0)
When you have a one liner, return is implicit, which means it's implied, as opposed to explicit.
The accumulator is what your value starts at while it totals up the values.
Array.reduce(callback, accumulator)
I'm following an online course about Javascript Functional Programming
at the Exercise 16 it show you how reduce is actually implemented, in order to help you understand how to use it, but into this implementation there is something i don't actually get, i'll show the code:
Array.prototype.reduce = function(combiner, initialValue) {
var counter, accumulatedValue;
// If the array is empty, do nothing
if (this.length === 0) {
return this;
}
else {
// If the user didn't pass an initial value, use the first item.
if (arguments.length === 1) {
counter = 1;
accumulatedValue = this[0];
}
else if (arguments.length >= 2) {
counter = 0;
accumulatedValue = initialValue;
}
else {
throw "Invalid arguments.";
}
// Loop through the array, feeding the current value and the result of
// the previous computation back into the combiner function until
// we've exhausted the entire array and are left with only one value.
while(counter < this.length) {
accumulatedValue = combiner(accumulatedValue, this[counter])
counter++;
}
return [accumulatedValue];
}
};
I don't understand the first if statement, when it check for this.length what this actually mean?
Take note this is different from the reduce in ES5, which returns an value instead of an Array, this is used just as a sample for the learning purpose.
Array.prototype.reduce = function(...
is saying, "create a function on the prototype of Array" - this means that the new reduce function will be callable on all arrays, eg:
[1, 2, 3].reduce(...
This means you can also call it on empty arrays, eg:
[].reduce(...
Building on the comment:
If the array is empty, do nothing
You're working on an array, and when the function is called, this is set to the array that reduce was called on. This implementation of reduce assumes that if that array is empty (ie this.length === 0), you can't logically reduce it any further - there's nothing to reduce, so you can return the same empty array.
As pointed out by #Alnitak in the comments, this implementation of reduce is flawed as compared to the specification. A different implementation is available on the MDN for polyfilling older browsers.
For education purposes, I was trying to re-create Underscore.js's _.reduce() method. While I was able to do this in an explicit style using for loops. But this is far from ideal because it mutates the original list that was supplied as an argument, which is dangerous.
I also realized that creating such method using functional programming style is harder, since it is not possible to explicitly set i value for looping.
// Explicit style
var reduce = function(list, iteratee, initial) {
if (Array.isArray(list)) {
var start;
if (arguments.length === 3) {
start = initial;
for (var i = 0; i < list.length; i++) {
start = iteratee(start, list[i], i);
}
} else {
start = list[0];
for (var i = 1; i < list.length; i++) {
start = iteratee(start, list[i], i);
}
}
}
if (list.constructor === Object) {
var start;
if (arguments.length === 3) {
start = initial;
for (var key in list) {
start = iteratee(start, list[key], key);
}
} else {
start = list[Object.keys(list)[0]];
// Delete the first property to avoid duplication.
delete list[Object.keys(list)[0]];
for (var key in list) {
start = iteratee(start, list[key], key);
}
}
}
return start;
};
What makes me struggle is that when my reduce() is supplied with an argument, initial, I need to subsequently either skip or remove the first element or property of the argument, list for the final value that will be returned. Because not doing so will double count the first element/property. I can't think of how I could do such thing when creating the function with functional programming style, with _.each() or forEach() involved.
This is my functional style of reduce() that is working partially. It works correctly when memo(initial value) is supplied, because I don't need to skip the first element/property. But it's not working correctly when memo is not supplied, because then I'm setting memo to either first element or property, and I should be able to skip it during the looping, which I don't know how.
// Functional style (not working without memo)
var reduce = function(list, iteratee, memo) {
var memo = memo || list[0] || list[Object.keys(list)[0]];
_.each(list, function(element, index, list){
memo = iteratee(memo, element, index, list);
});
return memo;
};
I spent quite a long time searching for answers to my question on Google. But wasn't able to find one. I would really appreciate your advice. Thanks.
Lastly, this is an additional code I came up with which does not work, but I think it should.
var reduce = function(list, iteratee, memo) {
var collection = list;
var accumulation;
_.each(collection, function(item){
if (arguments.length < 3) {
if (Array.isArray(collection)) {
accumulation = collection[0];
collection.shift();
accumulation = iteratee(accumulation, item);
} else {
accumulation = collection[Object.keys(collection)[0]];
delete collection[Object.keys(collection)[0]];
accumulation = iteratee(accumulation, item);
}
} else {
accumulation = memo;
accumulation = iteratee(accumulation, item);
}
});
return accumulation;
};
Here is the shortest version I could come up with.
_.reduce = function(list, iteratee, memo){
var memoUndefined = arguments.length < 3;
_.each(list, function(elem, index, list){
if(memoUndefined) {
memoUndefined = false;
memo = elem;
} else memo = iteratee(memo, elem, index, list);
});
return memo;
};
Reduce accepts three parameters: A collection (array or object), callback, and accumulator (this one is optional).
Reduce iterates through the collection, which invokes the callback and keeps track of the result in the accumulator.
If an accumulator is not passed in, we'll set it to the first element of the collection.
If an accumulator is available, we'll set the accumulator to be equal to the result of invoking the callback and passing in the current accumulator and the current element of the collection. Remember: Javascript executes its operations in right-to-left order, meaning the right side of the operator occurs first before it gets assigned to the variable on the left.
_.reduce = function(collection, callback, accumulator){
_.each(collection, function(elem){
return accumulator === undefined ? accumulator = collection[0] : accumulator = callback(accumulator, elem);
});
return accumulator;
};
First, you need a way to determine whether reduce received an initial memo value when you are inside the function you pass to _.each. You could do this a number of ways. One way is to simply set a flag based on the length of arguments. You need to do this outside the _.each call because the function you pass to _.each will have its own arguments object, masking the arguments object for reduce.
Using your code as a starting point:
var reduce = function(list, iteratee, memo) {
var considerFirst = arguments.length > 2;
var memo = memo || list[0] || list[Object.keys(list)[0]];
_.each(list, function(element, index, list){
if (index > 0 || considerFirst) {
memo = iteratee(memo, element, index, list);
}
});
return memo;
};
This still isn't quite right, though. We also need to update how you are defaulting memo. Currently, if memo receives a falsy value (e.g. 0), we still set it to the fist element in the list, but we don't set the flag indicating to ignore the first element. This means reduce will process the first element twice.
To get this right, you need to change how you are defaulting memo, setting it only if no argument is passed in. You could do something like this:
var reduce = function(list, iteratee, memo) {
var considerFirst = true;
if (arguments.length < 3) {
memo = list[0];
considerFirst = false;
}
_.each(list, function(element, index, list){
if (index > 0 || considerFirst) {
memo = iteratee(memo, element, index, list);
}
});
return memo;
};
This way, you only set memo if no argument was passed.
Note that you don't need to initialize memo with var. Having memo as a parameter does all the initialization you need.
Also note that I removed support for using reduce on a plain object. When you pass an object to _.each, the value of the index parameter is not a numerical index but the key for that entry, which may or may not be an integer. This does not play well with our index > 0 check to see if we are looking at the first entry. There are ways around this, but it doesn't seem central to your question. Check out the actual underscore implementation if you want to see how to make it work.
Update: the implementation SpiderPig suggests doesn't rely on index and so would work with objects, not just arrays.
Lastly, it's worth pointing out that underscore's implementation of _.reduce uses a for loop and not _.each.
I was looking through Select2 (source code) and found each2 method prototype:
$.extend($.fn, {
each2 : function (c) {
var j = $([0]), i = -1, l = this.length;
while (
++i < l
&& (j.context = j[0] = this[i])
&& c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
);
return this;
}
});
My question is - how this method works? I mean - why there is while loop only with condition, without statement part? I'd really love to understand this method's flow.
When you put an expression inside a condition (for instance: if (i), if (i == null), if (++i), if (i < 2))) the expression get evaluated before its 'checked' is it true or false.
Live example:
we have var i = 0; now you call if (++i) console.log(i). The expression ++i return 1 (it is understood as truth in javascript [0 is not]) so console.log(i) logs 1.
Now let's say we have var i = 0 and if (++i && ++i) console.log(i). The first expressions returns 1 so the second is also called which returns 2. Both 1 and 2 are treated as truth so console.log(i) logs 2
Awesome. Let's have a look on the same example as above but before if we initialise var i = -1
. Now we call if (++i && ++i) console.log(i). The first ++i returns 0 which is falsity.
To continue you have to get how && works. Let me explain quickly: If you stack exp1 && exp2 && exp3 ... && expX then exp2 will be only executed(evaluated) when exp1 returns truth (for instance: true, 1, "some string"). exp3 will be executed only if exp2 is truthy, exp4 when exp3 is truth and so on...(until we hit expN)
Let's go back to f (++i && ++i) console.log(i) now. So the first ++i returns 0 which is falsity so second ++i isn't executed and whole condition is false so console.log(i) won't be executed (How ever incrementation was completed so i equals to 0 now).
Now when you get it I can tell you that while loop works the same in condition checking way. For instance var = -2 and while (++i) will be executed until ++i returns falsity (that is 0). while (++1) will be executed exactly 2 times.
TL;DR BELOW!!!
So how this works?
while (
++i < l
&& (j.context = j[0] = this[i])
&& c.call(j[0], i, j) !== false
);
I think the best way to explain is to rewrite it (:
while ( ++i < l ) {
if (!(j.context = j[0] = this[i])) break;
if (!(c.call(j[0], i, j) !== false)) break;
}
or ever less magical:
var i = 0;
while ( i < l ) {
if (!(j.context = j[0] = this[i])) break;
if (!(c.call(j[0], i, j) !== false)) break;
i++;
}
The purpose of the code is to add a new function each2 to the jQuery object that works similar to .each. The usage of this .each2 function is same as .each which executes over a jQuery object, takes a function to call back with 2 arg (index and value) and returns jQuery object.
The mysterious while loop,
while (
++i < l
&& (j.context = j[0] = this[i])
&& c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
);
return this;
}
Observations:
Continues "only if" all 3 conditions evaluates to true
First Condition: ++i < l, a simple iterate evaluates to true until the value of i is less than count of selected elements. [i is initialized to -1, because of the usage of ++i in next line and i is used as index in later references]
Second Condition: It is actually an initialization + null/undefined check. The condition is evaluated to true for all valid values and fails when there is an invalid context.
Third condition: The purpose of this condition allows you to break out of the loop if the call back function return false. Same as .each where return false inside the function will break the iteration.
As noted by OP, there is no statement after the while condition. Note the semicolon after the while loop, return this is only run after the while loop has been completed, and that is when any one of the three statements evaluate to false. If all you want to know is how the while loop is working, Filip Bartuzi's answer is probably the most complete. I will attempt to give a broader answer as to what the each2 method does.
As noted in the docs, it is just a more efficient version of jQuery's #each, or Array.prototype.forEach or Underscore's #each or lodash's #forEach that is designed specifically for select2's uses. It is used to iterate over any array or other iterable, wrapped in a jQuery object. So it is used for arrays of strings as well as jQuery collections.
The way it works is the scope (this) is the array that it was called on. It is given a function as an argument. This is exactly how the other each methods I previously mentioned are called. The function provided to each2 is called once for each item in the array. The while loop is sort of a hacky way to iterate over each item in the array, and call the function, setting the item as the context and passing the index in the array and the item as a jQuery object as the first and second arguments. Each statement in the while loop condition must be evaluated to determine if it is true, and that process is being used to actually assign values to variables, and increment i. The c.call(j[0], i, j) !== false part allows the function to terminate the loop early by returning false. If the function returns false, the while loop will stop, otherwise it will continue until i is greater than l, meaning that every item in the array has been used. Returning this afterwards just enables another method to be chained to the array after .each2.
Basically the while loop could be rewritten to:
var j = $([0]), i = 0, l = this.length, continue = true;
while (i < l) {
i++;
j.context = j[0] = this[i];
continue = c.call(j[0], i, j);
if (!continue) {
break;
}
}
but there are probably some performance reasons why that can't be optimized as well by the browser.
It is more fragile than the normal each methods. For example if an element in the array has a falsey value such as 0, (j.context = j[0] = this[i]) will be falsey, causing the loop to terminate. But it is only used in the specialized cases of the select2 code, where that shouldn't happen.
Let's walk through a couple of examples.
function syncCssClasses(dest, src, adapter) {
var classes, replacements = [], adapted;
classes = $.trim(dest.attr("class"));
if (classes) {
classes = '' + classes; // for IE which returns object
$(classes.split(/\s+/)).each2(function() {
if (this.indexOf("select2-") === 0) {
replacements.push(this);
}
});
}
...
^ This code is getting the classes from a dest DOM element. The classes are split into an array of strings (the string is being split on whitespace characters, that's the \s+ regular expression), each class name is an item in the array. No special jQuery work is needed here, so the 2 arguments that the function called by each2 is provided are not used. If the class starts with 'select2-' the class is added into a array called replacements. The 'select2-' classes are being copied, pretty simple.
group.children=[];
$(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
^ For brevity I have not included the rest of this method, but it is part of the process method. In this case, each2 is being used to recursively process a node and all it's children. $(datum.children) is a jQuery selection, each 'child' (named childDatum) will be processed in turn, and it's children will go through the same process. The group.children array will be used as a collection, whatever is added to that array for each childDatum will be available, so after the each2 is run it will hold everything that has been added during the processing of all the children, grandchildren, etc.