Can't get this reduce function to work - javascript

Working through Eloquent JavaScript and High Order Functions - section in Functional Programming.
Trying to define a reduce function and use it in a higher order function countWords();, which takes a given array and counts number of times each particular value is present and puts it in an object.
I.e. this works:
function combine(countMap, word) {
countMap[word] = ++countMap[word] || 1; // made the edit
return countMap;
}
function countWords(wordArray) {
return wordArray.reduce(combine, {});
}
var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear'];
countWords(inputWords); // {Apple: 2, Banana: 1, Pear: 3}
I.e. this does not:
function combine(countMap, word) {
countMap[word] = ++countMap[word] || 1;
return countMap;
}
function forEach(array, action) {
for (var i = 0; i < array.length; i++) {
action(array[i]);
}
}
function reduce(fn, base, array) {
forEach(array, function (element) {
base = fn(base, element);
});
return base;
}
function countWords(wordArray) {
return reduce(combine, {}, wordArray);
}
var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear'];
countWords(inputWords); // returned this - [object Object] { ... } - this is no longer an issue after fix keeping it noted for reference to the original issue.
Any help on this would be great. Thanks.

Your original reduce is actually broken, despite you saying that it works.
Here's a reduce that actually functions
var words = ["foo", "bar", "hello", "world", "foo", "bar"];
var wordIndexer = function(map, word) {
map[word] = map[word] || 0;
map[word]++;
return map;
};
var count = word.reduce(wordIndexer, {});
console.log(count);
// Object {foo: 2, bar: 2, hello: 1, world: 1}
That said, I'm not entirely sure what you're trying to do with the second half of your post. Are you just trying to write implementations for forEach and reduce so you can understand how they work?
I would write the forEach like this
var forEach = function(arr, callback) {
for (var i=0, len=arr.length; i<len; i++) {
callback(arr[i], i, arr);
}
return arr;
};
And reduce like this
var reduce = function(arr, callback, initialValue) {
var result = initialValue;
forEach(arr, function(elem, idx) {
result = callback(result, elem, idx, arr);
});
return result;
};
Test them out
var numbers = [10, 20, 30];
forEach(numbers, function(num, idx) {
console.log(idx, num);
});
// 0, 10
// 1, 20
// 2, 30
//=> [10, 20, 30]
var n = reduce(numbers, function(sum, num, idx, arr) {
return sum = sum + num;
}, 0);
console.log(n);
//=> 60
For those curious about reduce callback, I matched the native .reduce callback

I guess it depends on if you want forEach and reduce to be similar (simple) or as close as possible/reasonable to the ECMA5 spec (ignoring browser bugs), I like close as possible/reasonable.
Array.prototype.forEach ( callbackfn [ , thisArg ] )
callbackfn should be a function that accepts three arguments. forEach calls callbackfn once for each element present in the array, in ascending order. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.
callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.
forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by forEach is set before the first call to callbackfn. Elements which are appended to the array after the call to forEach begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callback will be the value at the time forEach visits them; elements that are deleted after the call to forEach begins and before being visited are not visited.
When the forEach method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If thisArg was supplied, let T be thisArg; else let T be undefined.
Let k be 0.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Call the [[Call]] internal method of callbackfn with T as the this value and argument list containing kValue, k, and O.
Increase k by 1.
Return undefined.
The length property of the forEach method is 1.
NOTE The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the forEach function can be applied successfully to a host object is implementation-dependent.
-
Array.prototype.reduce ( callbackfn [ , initialValue ] )
callbackfn should be a function that takes four arguments. reduce calls the callback, as a function, once for each element present in the array, in ascending order.
callbackfn is called with four arguments: the previousValue (or value from the previous call to callbackfn), the currentValue (value of the current element), the currentIndex, and the object being traversed. The first time that callback is called, the previousValue and currentValue can be one of two values. If an initialValue was provided in the call to reduce, then previousValue will be equal to initialValue and currentValue will be equal to the first value in the array. If no initialValue was provided, then previousValue will be equal to the first value in the array and currentValue will be equal to the second. It is a TypeError if the array contains no elements and initialValue is not provided.
reduce does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by reduce is set before the first call to callbackfn. Elements that are appended to the array after the call to reduce begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them; elements that are deleted after the call to reduce begins and before being visited are not visited.
When the reduce method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If len is 0 and initialValue is not present, throw a TypeError exception.
Let k be 0.
If initialValue is present, then
Set accumulator to initialValue.
Else, initialValue is not present
Let kPresent be false.
Repeat, while kPresent is false and k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let accumulator be the result of calling the [[Get]] internal method of O with argument Pk.
Increase k by 1.
If kPresent is false, throw a TypeError exception.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Let accumulator be the result of calling the [[Call]] internal method of callbackfn with undefined as the this value and argument list containing accumulator, kValue, k, and O.
Increase k by 1.
Return accumulator.
The length property of the reduce method is 1.
NOTE The reduce function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the reduce function can be applied successfully to a host object is implementation-dependent.
Which for me I would write (and these are not 100% to spec, but close) and keep in my personal library.
function firstToCapital(inputString) {
return inputString.charAt(0).toUpperCase() + inputString.slice(1).toLowerCase();
}
function isClass(inputArg, className) {
return Object.prototype.toString.call(inputArg) === '[object ' + firstToCapital(className) + ']';
}
function checkObjectCoercible(inputArg) {
if (typeof inputArg === 'undefined' || inputArg === null) {
throw new TypeError('Cannot convert argument to object');
}
return inputArg;
};
function ToObject(inputArg) {
checkObjectCoercible(inputArg);
if (isClass(inputArg, 'boolean')) {
inputArg = new Boolean(inputArg);
} else if (isClass(inputArg, 'number')) {
inputArg = new Number(inputArg);
} else if (isClass(inputArg, 'string')) {
inputArg = new String(inputArg);
}
return inputArg;
}
function ToUint32(inputArg) {
return inputArg >>> 0;
}
function throwIfNotAFunction(inputArg) {
if (!isClass(inputArg, 'function')) {
throw TypeError('Argument is not a function');
}
return inputArg;
}
function forEach(array, fn, thisArg) {
var object = ToObject(array),
length,
index;
throwIfNotAFunction(fn);
length = ToUint32(object.length);
for (index = 0; index < length; index += 1) {
if (index in object) {
fn.call(thisArg, object[index], index, object);
}
}
}
function reduce(array, fn, initialValue) {
var object = ToObject(array),
accumulator,
length,
kPresent,
index;
throwIfNotAFunction(fn);
length = ToUint32(object.length);
if (!length && arguments.length === 2) {
throw new TypeError('reduce of empty array with no initial value');
}
index = 0;
if (arguments.length > 2) {
accumulator = initialValue;
} else {
kPresent = false;
while (!kPresent && index < length) {
kPresent = index in object;
if (kPresent) {
accumulator = object[index];
index += 1;
}
}
if (!kPresent) {
throw new TypeError('reduce of empty array with no initial value');
}
}
while (index < length) {
if (index in object) {
accumulator = fn.call(undefined, accumulator, object[index], index, object);
}
index += 1;
}
return accumulator;
}
function keys(object) {
if (!isClass(object, 'object') && !isClass(object, 'function')) {
throw new TypeError('Argument must be an object or function');
}
var props = [],
prop;
for (prop in object) {
if (object.hasOwnProperty(prop)) {
props.push(prop);
}
}
return props;
}
var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear'];
var counts = reduce(inputWords, function (previous, element) {
previous[element] = ++previous[element] || 1;
return previous;
}, {});
forEach(keys(counts), function (key) {
console.log(key, this[key]);
}, counts);
On jsFiddle
Of course this may be a little OTT for what you are doing. :)

The reason is that your ForEach implementation is wrong. you should set i = 0;
function forEach(array, action) {
for(var i = 0; i < array.length; i++) {
action(array[i]);
}
}
There seems to be something wrong. You update an object. ++countMap
function combine(countMap, word) {
countMap[word] = ++countMap || 1;
return countMap;
}
It should be
function combine(countMap, word) {
countMap[word] = ++countMap[word] || 1;
return countMap;
}
I add a jsbin here

Related

Meaning of (x => x) in JavaScript arrow function using array.every() method

looking for an explanation on this line of code. I understand arrow functions to a degree. The purpose of this code snippet/challenge is; "Given any number of parameters, return true if none of the arguments are falsy." I've seen the solution like this:
const nothingIsNothing = (...args) => args.every(x => x)
Examples of arguments and the expected results are:
nothingIsNothing(0, false, undefined, null) ➞ false
nothingIsNothing(33, "Hello", true, []) ➞ true
nothingIsNothing(true, false) ➞ false
I just don't understand how the section (x => x) evaluates to either truthy or falsy. Can someone explain how this works? I hope this makes sense lol. Thanks!
With .every, if any of the return values from the callback are falsey, the .every evaluates to false, otherwise it evaluates to true. So x => x as a callback means: take every value in the array and return it immediately. If all are truthy, the whole .every evaluates to true, else false.
It's doing the same logic as this:
const nothingIsNothing = (...args) => {
for (const arg of args) {
if (!arg) return false;
}
return true;
};
Or, implementing something similar to .every yourself:
// don't do this in real code, this is just an example
Array.prototype.myEvery = function(callback) {
for (const item of this) {
if (!callback(item)) return false;
}
return true;
};
console.log([1, 2, 3].myEvery(x => x));
console.log([1, 2, 3, 0].myEvery(x => x));
Its a combinations of a couple of things
Javascript implicit return statement.
getVal = a => a;
is the same as
function getVal(a) { return a }
every web API method run until it encounters a falsy (not false) value. Below is a quote from MDN.
The every method executes the provided callback function once for each
element present in the array until it finds the one where callback
returns a falsy value. If such an element is found, the every method
immediately returns false. Otherwise, if callback returns a truthy
value for all elements, every returns true.
param => param is the same as (param) => { return param }
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
These are all the falsy values in JavaScript: https://developer.mozilla.org/en-US/docs/Glossary/Falsy
The documentation for the return value of every states the following:
true if the callback function returns a truthy value for every array
element. Otherwise, false.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
So something like this will be false as not all elements are truthy
const x = ['test', 0, null, -0].every(el => el);
console.log(x);
But something like this will return true as all elements are truthy values
const x = ['test', 1, 'hi', 10].every(el => el);
console.log(x);
As per the docs the every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value i.e true if the callback function returns a truthy value for every array element. Otherwise, false.
const nothingIsNothing = (...args) => args.every(x=>x) can be expanded to:
const nothingIsNothing = (...args) => args.every(function(x){
if(x)
return true
else
return false
})
Here is another version which will help you understand the shorthand better. The value x is typecasted to a boolean and true/false is returned.
const nothingIsNothing = (...args) => args.every(Boolean)
console.log(nothingIsNothing(0, false, undefined, null))
console.log(nothingIsNothing(33, "Hello", true, []))
The Array.prototpe.every() does the heavy lifting here.
The every() method tests whether all elements in the array pass the
test implemented by the provided function. It returns a Boolean value.
The PolyFil following the ECMA-262, 5th specification, assuming Object and TypeError have their original values, and that callbackfn.call evaluates to the original value of Function.prototype.call, demonstrate the internal behavior nicely:
Array.prototype.PolyFillEvery = function(callbackfn, thisArg) {
'use strict';
var T, k;
if (this == null) {
throw new TypeError('this is null or not defined');
}
// 1. Let O be the result of calling ToObject passing the this
// value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal method
// of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
if (typeof callbackfn !== 'function' && Object.prototype.toString.call(callbackfn) !== '[object Function]') {
throw new TypeError();
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0.
k = 0;
// 7. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal
// method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
var testResult;
// i. Let kValue be the result of calling the Get internal method
// of O with argument Pk.
kValue = O[k];
// ii. Let testResult be the result of calling the Call internal method
// of callbackfn with T as the this value if T is not undefined
// else is the result of calling callbackfn
// and argument list containing kValue, k, and O.
if(T) testResult = callbackfn.call(T, kValue, k, O);
else testResult = callbackfn(kValue,k,O)
// iii. If ToBoolean(testResult) is false, return false.
if (!testResult) {
return false;
}
}
k++;
}
return true;
};
const arFalsey = [0, false, undefined, null];
const arTruthy = [33, "Hello", true, []];
console.log(arFalsey.PolyFillEvery(x=>x)); //false
console.log(arTruthy.PolyFillEvery(x=>x)); //true
console.log([true,false].PolyFillEvery(x=>x)); //false
The crucial part is if-clause in the inside of the loop:
// iii. If ToBoolean(testResult) is false, return false.
if (!testResult) {
return false;
}
As soon as one of the elements is not tested truthy false is returned; the spread (...) expression is simply used to iterate the array and instead of a traditional function an arrow function x => x is used to feed (via return) each element as is to the test in the every function.

Clarification on filter() function; how it works

So, I am self-learning HTML, CSS, JavaScript. I was going through arrow functions and found the following code on MDN website but I am not sure if I understand it clearly how the filter() function works. This is how I understand it: the "word" is the parameter of testFunct() and the arguments are the elements of wrds array and they are passed to testFunct(word). Is it like filter function loops through each elements of the array(arguments) and assesses the requirement(word.length > 6)? Like with normal(to me that's normal as I am a beginner) argument/parameter pair, let's say you pass 2 arguments and there are 2 parameters to receive them. Thank you.
var wrds = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
//const result = words.filter(word => word.length > 6);
//write the arrow function as full function to see if you understood how it works.
const result = wrds.filter(function testFunct(word) {return word.length > 6;});
console.log(result);
prints 'exuberant', 'destruction', 'present
The polyfill on the very same MDN page represents the algorithm exactly equivalent to the one specified in ECMA-262, 5th edition:
if (!Array.prototype.filter){
Array.prototype.filter = function(func, thisArg) {
'use strict';
if ( ! ((typeof func === 'Function' || typeof func === 'function') && this) )
throw new TypeError();
var len = this.length >>> 0,
res = new Array(len), // preallocate array
t = this, c = 0, i = -1;
if (thisArg === undefined){
while (++i !== len){
// checks to see if the key was set
if (i in this){
if (func(t[i], i, t)){
res[c++] = t[i];
}
}
}
}
else{
while (++i !== len){
// checks to see if the key was set
if (i in this){
if (func.call(thisArg, t[i], i, t)){
res[c++] = t[i];
}
}
}
}
res.length = c; // shrink down array to proper size
return res;
};
}
So, yes it iterates the array with a while loop.
Is it like filter function loops through each elements of the array(arguments) and assesses the requirement(word.length > 6)?
Yes. (For a value of "assesses" equal to "Calls the function with the array element as an argument and tests the response for truthiness).
let's say you pass 2 arguments and there are 2 parameters to receive them.
The only argument you pass is the argument to filter() which is a function.
It is filter() that calls that function, it passes three arguments (the current array element, the index of that element, and the whole array).
The function filter is like a for loop, that takes each element of the array and filters it with a function. Let's say:
var words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
That takes 'spray', then limit, then elite and so on
spray.length > 6 = false
limit.length > 6 = false
elite.length > 6 = false
exuberant.length > 6 = true
destruction.length > 6 = true
present.length > 6 = true
Takes all trues and remap them in the new array

How are array methods aware of the values they are called on?

Can someone explain to me how are array methods aware of the value that we called them on,
As part of the prototype inheritance shouldn't it exist on the Array.prototype
if we say
let animals = ['dog','cat']
animals.map( x => console.log(x))
//dog
//cat
I just don't understand how is map aware of that we passed ['dog','cat'].
I usually see that if you call a function then you need to call it like map(animals).
thank you in advance
or looking at the map polyfill where is the line that we assign it to the arguments of the array.
if (!Array.prototype.map) {
Array.prototype.map = function(callback/*, thisArg*/) {
var T, A, k;
if (this == null) {
throw new TypeError('this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = arguments[1];
}
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while (k < len) {
var kValue, mappedValue;
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
// For best browser support, use the following:
A[k] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
Array.prototype.mymap = function(callback) {
var myArray = this; // here is the magic
var newArray = [];
for (var i = 0; i < myArray.length; i++) {
newArray.push(callback(myArray[i]));
}
return newArray;
};
console.log([0, 1, 2, 3].mymap(a => a * 2));
Well if we looked at the definition of Map according to W3Schools:
The map() method creates a new array with the results of calling a
function for every array element.
So the function map checks every element and does something with that.
In this example you say: "Console.log every element in this array"
So it's logic that the function map is aware what is in the array.
map() is a method of the Array object. In JavaScript, we say that the animals object uses "prototypical inheritance" of the Array object. It's kind of like saying animals inherits the properties and method of the Array base class (if you come from a world of OOP).
Since map is a function that exists for all Array objects, animals is no exception.
By definition map() takes a function, and returns an Array. It then iterates through each element of the array it was called from (in this case the animals array), and applies the passed function to each element.
Usually you would use this with a return statement in order to generate a new array. For instance, you could write something like:
var upperCaseAnimals = animals.map(x => return(x.toUpperCase()))
This would create a new array, upperCaseAnimals which contains all elements of the animals array with toUpperCase() applied to them.
But you can also use it to "do something" with each element of the array. This is what's being done in the sample above. Instead of giving a return value for each element, you're just console.log()ing each element.

Is the order of iteration for javascript array methods (map, forEach, reduce, etc) deterministic?

Is the order of iterating through an array using one of the native methods (map, forEach, reduce, filter, etc) deterministic and guaranteed by the standard?
EG, are foo, bar, baz, and qux guaranteed to be [0, 2, 6, 12]?
const a = [1, 2, 3, 4];
const foo = a.map((item, index) => item * index);
const bar = []; a.forEach((item, index) => bar[index] = item * index);
const baz = []; a.reduce((total, item, index) => baz[index] = item * index, 0);
const qux = []; a.filter((item, index) => qux[index] = item * index);
// etc
(These are (very) contrived examples)
The callback function is called for each element present in the array, in ascending order. It is not called for missing elements. (Missing elements? Yes, JavaScript handle sparse arrays)
var test = [];
test[30] = 'Test'; // sparse array, only one element defined.
test.forEach(
function(value){
console.log(value); // will only be called one time.
}
);
From the standard: ECMA-262
22.1.3.10 Array.prototype.forEach ( callbackfn [ , thisArg ] )
NOTE 1
callbackfn should be a function that accepts three arguments.
forEach calls callbackfn once for each element present in the array, in ascending order. callbackfn is called only for elements of
the array which actually exist; it is not called for missing elements
of the array
If a thisArg parameter is provided, it will be used as the
this value for each invocation of callbackfn. If it is not provided, undefined is used instead.
callbackfn is called with three arguments: the value of the
element, the index of the element, and the object being traversed.
forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
When the forEach method is called with one or two arguments, the
following steps are taken:
Let O be ? ToObject(this value).
Let len be ? ToLength(? Get(O, "length")).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If thisArg was supplied, let T be thisArg; else let T be undefined.
Let k be 0.
Repeat, while k < len a. Let Pk be ! ToString(k). b. Let kPresent be ? HasProperty(O, Pk). c. If kPresent is true, then i. Let kValue be
? Get(O, Pk). ii. Perform ? Call(callbackfn, T, « kValue, k, O »). d.
Increase k by 1.
Return undefined.

Rebuilding reduce and each

Recently had an interview for a programming related gig. The interviewer made me rewrite each first, this I had no problem with and understood completely. Heres my each method:
var each = function(arrayItems,callback){
//if arrayItems is not an array
if(!Array.isArray(arrayItems)){
//write a for in loop to iterate through the object
for(var key in arrayItems){
//store the value, the key and the whole object as the callback parameters
callback(arrayItems[key],key,arrayItems);
}
}
//if arrayItems wasn't an object iterate through the array
for(var i = 0; i < arrayItems.length; i++){
//store the value,key and whole array as the callback's parameters
callback(arrayItems[i],i,arrayItems);
}
};
Second he had my rewrite reduce this no matter how many times I research devdocs and pick apart every piece of code I have trouble understanding here's my method:
var reduce = function(array,callback,initialValue){
// Implementing each into reduce
each(array,function(number){
//This is the line of code that confuses me
initialValue = callback(initialValue,number);
});
};
I'm looking for somebody to elaborate on the second line of code in reduce. How does this work initialValue is a function with initialValue and number as the parameters. Do initialValue and number need to be in a certain order? How does it work that initial value is equal to a function with intialValue and number as the callback? Also how does it know to execute the code if its something like : [number(s)].reduce(function(a,b){return a + b;},intialValue) I know these questions may seem a little vague, the interview is long gone but as a personal challenge I want to understand what is going on better.
"How does this work initialValue is a function with initialValue and number as the parameters."
I assume you mean callback is a function. If so, yes, it will be passed arguments, but more than just two.
The first is what you have as initialValue, though this adds confusion, because after the first time you update it, it's no longer the initial value. Better to use a different variable name.
The second is the current item in the iteration. You have it a name number, but it may not be a number. Again a confusing name.
The third should be the index of the current iteration.
The fourth should be the original collection.
"Do initialValue and number need to be in a certain order?"
Of course. That's the "accumulator" of your final value and the value of the current iteration. The user needs to know which is which.
"How does it work that initial value is equal to a function with intialValue and number as the callback?"
Every iteration, the callback is supposed to return the updated value of the accumulator. That value gets passed as the first argument of the next iteration.
"Also how does it know to execute the code if its something like : [number(s)].reduce(function(a,b){return a + b;},intialValue)"
That's an odd example, but if you had a correct Array, and you passed something like 0, then a is the accumulator, which will be equal to either the initialValue that you passed, or the return value of the previous iteration.
A more reasonable example of the use of .reduce() would be:
var result = [2,4,3,8,6].reduce(function(acc, curr, i, arr) {
console.log(acc, curr);
return acc + curr;
}, 0);
So on the first iteration, acc (short for accumulator) will be 0 (the initial value given), and curr (short for current) will be the number 2, which is the first item in the Array.
The return value is then 0 + 2.
On the second iteration, acc is the value of the last return, which was 0 + 2 or 2, and curr is the second item in the Array, which is 4.
The return value is then 2 + 4.
On the third iteration, acc is the value of the last return, which was 2 + 4 or 6, and curr is the third item in the Array, which is 3.
The return value is then 6 + 3.
...and so on.
So as you can see, it's just taking either the intial value, or the return value of the previous iteration, passing it as the first argument, letting you do whatever manipulation you want, and then taking your return value and passing it as the first argument of the next iteration.
This continues until the loop is complete, at which point .reduce() gives you whatever you returned from the last iteration.
Note that neither of your functions are ECMAScript compliant. The first one operates on plain objects??? And both are missing certain details.
You may check the underscore core functions!
if (typeof (/./) !== 'function') {
_.isFunction = function(obj) {
return typeof obj === 'function';
};
}
_.bind = function(func, context) {
var args, bound;
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
_.keys = function(obj) {
if (!_.isObject(obj)) return [];
if (nativeKeys) return nativeKeys(obj);
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys.push(key);
return keys;
};
_.each = function(obj, iterator, context) {
if (obj == null) return obj;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
return obj;
};
_.reduce = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo;
};
I guess it depends on if you want forEach and reduce to be similar (simple but not to spec) or as close as possible/reasonable to the ECMA5 spec. To best understand them, I would suggest reading the specifications.
Array.prototype.forEach ( callbackfn [ , thisArg ] )
callbackfn should be a function that accepts three arguments. forEach calls callbackfn once for each element present in the array, in ascending order. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.
callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.
forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by forEach is set before the first call to callbackfn. Elements which are appended to the array after the call to forEach begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callback will be the value at the time forEach visits them; elements that are deleted after the call to forEach begins and before being visited are not visited.
When the forEach method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If thisArg was supplied, let T be thisArg; else let T be undefined.
Let k be 0.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Call the [[Call]] internal method of callbackfn with T as the this value and argument list containing kValue, k, and O.
Increase k by 1.
Return undefined.
The length property of the forEach method is 1.
NOTE The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the forEach function can be applied successfully to a host object is implementation-dependent.
-
Array.prototype.reduce ( callbackfn [ , initialValue ] )
callbackfn should be a function that takes four arguments. reduce calls the callback, as a function, once for each element present in the array, in ascending order.
callbackfn is called with four arguments: the previousValue (or value from the previous call to callbackfn), the currentValue (value of the current element), the currentIndex, and the object being traversed. The first time that callback is called, the previousValue and currentValue can be one of two values. If an initialValue was provided in the call to reduce, then previousValue will be equal to initialValue and currentValue will be equal to the first value in the array. If no initialValue was provided, then previousValue will be equal to the first value in the array and currentValue will be equal to the second. It is a TypeError if the array contains no elements and initialValue is not provided.
reduce does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
The range of elements processed by reduce is set before the first call to callbackfn. Elements that are appended to the array after the call to reduce begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them; elements that are deleted after the call to reduce begins and before being visited are not visited.
When the reduce method is called with one or two arguments, the following steps are taken:
Let O be the result of calling ToObject passing the this value as the argument.
Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
Let len be ToUint32(lenValue).
If IsCallable(callbackfn) is false, throw a TypeError exception.
If len is 0 and initialValue is not present, throw a TypeError exception.
Let k be 0.
If initialValue is present, then
Set accumulator to initialValue.
Else, initialValue is not present
Let kPresent be false.
Repeat, while kPresent is false and k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let accumulator be the result of calling the [[Get]] internal method of O with argument Pk.
Increase k by 1.
If kPresent is false, throw a TypeError exception.
Repeat, while k < len
Let Pk be ToString(k).
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
If kPresent is true, then
Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Let accumulator be the result of calling the [[Call]] internal method of callbackfn with undefined as the this value and argument list containing accumulator, kValue, k, and O.
Increase k by 1.
Return accumulator.
The length property of the reduce method is 1.
NOTE The reduce function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the reduce function can be applied successfully to a host object is implementation-dependent.
Which for me I would write (and these are not 100% to spec, but close). You could of course write them exactly as described in the specs above.
Helper functions
function firstToCapital(inputString) {
return inputString.charAt(0).toUpperCase() + inputString.slice(1).toLowerCase();
}
function isClass(inputArg, className) {
return Object.prototype.toString.call(inputArg) === '[object ' + firstToCapital(className) + ']';
}
function throwIfNotAFunction(inputArg) {
if (!isClass(inputArg, 'function')) {
throw TypeError('Argument is not a function');
}
return inputArg;
}
Abstracts described in the speciications
function checkObjectCoercible(inputArg) {
if (typeof inputArg === 'undefined' || inputArg === null) {
throw new TypeError('Cannot convert argument to object');
}
return inputArg;
};
function ToObject(inputArg) {
checkObjectCoercible(inputArg);
if (isClass(inputArg, 'boolean')) {
inputArg = new Boolean(inputArg);
} else if (isClass(inputArg, 'number')) {
inputArg = new Number(inputArg);
} else if (isClass(inputArg, 'string')) {
inputArg = new String(inputArg);
}
return inputArg;
}
function ToUint32(inputArg) {
return inputArg >>> 0;
}
forEach
function forEach(array, fn, thisArg) {
var object = ToObject(array),
length,
index;
throwIfNotAFunction(fn);
length = ToUint32(object.length);
for (index = 0; index < length; index += 1) {
if (index in object) {
fn.call(thisArg, object[index], index, object);
}
}
}
reduce
function reduce(array, fn, initialValue) {
var object = ToObject(array),
accumulator,
length,
kPresent,
index;
throwIfNotAFunction(fn);
length = ToUint32(object.length);
if (!length && arguments.length === 2) {
throw new TypeError('reduce of empty array with no initial value');
}
index = 0;
if (arguments.length > 2) {
accumulator = initialValue;
} else {
kPresent = false;
while (!kPresent && index < length) {
kPresent = index in object;
if (kPresent) {
accumulator = object[index];
index += 1;
}
}
if (!kPresent) {
throw new TypeError('reduce of empty array with no initial value');
}
}
while (index < length) {
if (index in object) {
accumulator = fn.call(undefined, accumulator, object[index], index, object);
}
index += 1;
}
return accumulator;
}
On jsFiddle

Categories

Resources