Related
Having some difficulty with this code for my assignment.
I'm supposed to create two functions.
the first function is called calledInLoop that will accept one parameter and log the parameter.
calledInLoop = function (parameter) {
console.log(parameter);
}
the second function is called loopThrough that will accept an array, loop through each, and invoke the calledInLoop function. The result should be each element of the array is console logged.
loopThrough = function (array) {
for (var i = 0; i < array.length; i++){
calledInLoop(array[i]);
};
}
myArray = ['dog', 'bird', 'cat', 'gopher'];
console.log(loopThrough(myArray)); returns each element on its own console.log line but then returns undefined. Why is this?
The call to console.log in console.log(loopThrough(myArray)); is only printing out undefined. It does this because loopThrough does not return anything, so it defaults to undefined.
The elements in the array are printed by a call to calledInLoop in loopThrough, which in turn calls console.log.
Your loopThrough function does not return any value when called. Hence it's return value is undefined.
loopThrough = function (array) {
for (var i = 0; i < array.length; i++)
calledInLoop(array[i])
return 1
}
Now this will return you 1.
Similarly you can return any other values.
Let's say I have
$.each(data,function(i,item){
...
});
I get that $.each is going to iterate over the object (or array) data but I am confused about the function. Why is "i" incrementing by 1 ("i"++) over the loop and what defines "item" to be data[i]? Is this just a built in function mechanic applied to every item in the object or array?
Thanks
jQuery's .each() function calls your callback function with element's index as first parameter and element's value as second parameter.
You can find relevant documentation here:
http://devdocs.io/jquery/jquery.each
Here is a "hypothetical implementation" that shows how this can be achieved:
var myEachFunction = function (array, callback) {
var i, len;
for (i = 0, len = array.length; i < len; ++i) {
callback(i, array[i]);
}
};
// Usage: myEachFunction([5, 6, 7], function (i, item) {
// doSomething();
// });
In this implementation, I am calling the provided callback function with the loop index and the item value, in that order.
jQuery's implementation is similar, but much more sophisticated. Besides things like error/argument checking, it also supports iterating over objects, not just arrays. The jQuery documentation provides more information on what arguments are passed in what order.
It's exactly a built in jquery function, for each item (looped with a for it applies the function that you provided, passing to the function index and element[index]). Also it's smarter than that it detects if the first argument in $.each is a json or array - for array - the index is passed for json the key is passed along with the values).
A similar implementation would look like this:
function each(x, fn) {
if(x.constructor == Array) {
for(var i = 0; i < x.length; i++) {
fn(i, x[i]);
}
} else if (x.constructor == Object) {
for(var key in x) {
fn(key, x[key]);
}
} else {
throw Exception('Non array or object passed');
}
}
each([1,2,3], function(ind, el) {console.log(ind, el)});
each({x:1, y:2}, function (key, el) { console.log(key, el) });
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
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
I wrote a simple map implementation for some task. Then, out of curiosity, I wrote two more. I like map1 but the code is kinda hard to read. If somebody is interested, I'd appreciate a simple code review.
Which one is better? Do you know some other way to implement this in javascript?
var map = function(arr, func) {
var newarr = [];
for (var i = 0; i < arr.length; i++) {
newarr[i] = func(arr[i]);
}
return newarr;
};
var map1 = function(arr, func) {
if (arr.length === 0) return [];
return [func(arr[0])].concat(funcmap(arr.slice(1), func));
};
var map2 = function(arr, func) {
var iter = function(result, i) {
if (i === arr.length) return result;
result.push(func(arr[i]));
return iter(result, i+1);
};
return iter([], 0);
};
Thanks!
EDIT
I am thinking about such function in general.
For example, right now I am going to use it to iterate like this:
map(['class1', 'class2', 'class3'], function(cls) {
el.removeClass(cls);
});
or
ids = map(elements, extract_id);
/* elements is a collection of html elements,
extract_id is a func that extracts id from innerHTML */
What about the map implementation used natively on Firefox and SpiderMonkey, I think it's very straight forward:
if (!Array.prototype.map) {
Array.prototype.map = function(fun /*, thisp*/) {
var len = this.length >>> 0; // make sure length is a positive number
if (typeof fun != "function") // make sure the first argument is a function
throw new TypeError();
var res = new Array(len); // initialize the resulting array
var thisp = arguments[1]; // an optional 'context' argument
for (var i = 0; i < len; i++) {
if (i in this)
res[i] = fun.call(thisp, this[i], i, this); // fill the resulting array
}
return res;
};
}
If you don't want to extend the Array.prototype, declare it as a normal function expression.
As a reference, map is implemented as following in jQuery
map: function( elems, callback ) {
var ret = [];
// Go through the array, translating each of the items to their
// new value (or values).
for ( var i = 0, length = elems.length; i < length; i++ ) {
var value = callback( elems[ i ], i );
if ( value != null )
ret[ ret.length ] = value;
}
return ret.concat.apply( [], ret );
}
which seems most similar to your first implementation. I'd say the first one is preferred as it is the simplest to read and understand. But if performance is your concern, profile them.
I think that depends on what you want map to do when func might change the array. I would tend to err on the side of simplicity and sample length once.
You can always specify the output size as in
var map = function(arr, func) {
var n = arr.length & 0x7fffffff; // Make sure n is a non-neg integer
var newarr = new Array(n); // Preallocate array size
var USELESS = {};
for (var i = 0; i < n; ++i) {
newarr[i] = func.call(USELESS, arr[i]);
}
return newarr;
};
I used the func.call() form instead of just func(...) instead since I dislike calling user supplied code without specifying what 'this' is, but YMMV.
This first one is most appropriate. Recursing one level for every array item may make sense in a functional language, but in a procedural language without tail-call optimisation it's insane.
However, there is already a map function on Array: it is defined by ECMA-262 Fifth Edition and, as a built-in function, is going to be the optimal choice. Use that:
alert([1,2,3].map(function(n) { return n+3; })); // 4,5,6
The only problem is that Fifth Edition isn't supported by all current browsers: in particular, the Array extensions are not present in IE. But you can fix that with a little remedial work on the Array prototype:
if (!Array.prototype.map) {
Array.prototype.map= function(fn, that) {
var result= new Array(this.length);
for (var i= 0; i<this.length; i++)
if (i in this)
result[i]= fn.call(that, this[i], i, this);
return result;
};
}
This version, as per the ECMA standard, allows an optional object to be passed in to bind to this in the function call, and skips over any missing values (it's legal in JavaScript to have a list of length 3 where there is no second item).
There's something wrong in second method. 'funcmap' shouldn't be changed to 'map1'?
If so - this method loses, as concat() method is expensive - creates new array from given ones, so has to allocate extra memory and execute in O(array1.length + array2.length).
I like your first implementation best - it's definitely easiest to understand and seems quick in execution to me. No extra declaration (like in third way), extra function calls - just one for loop and array.length assignments.
I'd say the first one wins on simplicity (and immediate understandability); performance will be highly dependent on what the engine at hand optimizes, so you'd have to profile in the engines you want to support.