I'm just learning how to use JS higher-order functions (map, forEach, reduce, etc), and have stumbled into confusion. I'm trying to write a simple 'range' function, but can't seem to populate my output array. This is the goal:
range(1, 4) // [1, 2, 3, 4]
I'm getting this:
[undefined × 4]
Here is my code:
function range(num1, num2) {
var rangeArr = new Array((num2 + 1) - num1);
return rangeArr.map(function(e, i, arr) {return arr[i] = num1 + i});
}
What am I missing here? As far as I can tell the problem appears to have something to do with the way I'm utilizing 'new Array', but beyond that I'm lost.
Oh, and here's the part that really confuses me. This works fine:
function bleck() {
var blah = [1, 2, 3, 4];
var x = 'wtf';
return blah.map(function(e, i, arr) {return arr[i] = x})
}
["wtf", "wtf", "wtf", "wtf"]
Thanks!!
The forEach method iterates over the indices of the array. Interestingly enough, when you create a new array via new Array(n), it contains no indices at all. Instead, it just sets its .length property.
> var a = new Array(3);
> console.info(a)
[]
> console.info([undefined, undefined, undefined])
[undefined, undefined, undefined]
MDN describes forEach, and specifically states:
forEach executes the provided callback once for each element of the
array with an assigned value. It is not invoked for indexes which have
been deleted or elided.
Here's a neat technique to get an array with empty, but existing, indices.
var a = Array.apply(null, Array(3));
This works because .apply "expands" the elided elements into proper arguments, and the results ends up being something like Array(undefined, undefined, undefined).
The array is defined with 4 entires each of which is undefined.
Map will not iterate over undefined entires, it skips them.
callback is invoked only for indexes of the array which have assigned
values; it is not invoked for indexes that are undefined, those which
have been deleted or which have never been assigned values.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
When you create a new Array(x) it is creating what is called a sparse array, which might behave a bit differently, as you can see, some browsers will say [undefined x 20,"foo", undefined x 5] if you just set one value, and I believe it doesn't iterate over those values.
The problem is that map doesn't iterate undefined entries (*).
I suggest using a for loop instead:
var rangeArr = new Array((num2 + 1) - num1);
for(var i=0; i<=num2-num1; ++i)
rangeArr[i] = num1 + i;
return rangeArr;
(*) With undefined entries I mean rangeArr.hasOwnProperty(i) === false, not to be confused with rangeArr[i] === void 0.
Related
I've observed this in Firefox-3.5.7/Firebug-1.5.3 and Firefox-3.6.16/Firebug-1.6.2
When I fire up Firebug:
var x = new Array(3)
console.log(x)
// [undefined, undefined, undefined]
var y = [undefined, undefined, undefined]
console.log(y)
// [undefined, undefined, undefined]
console.log( x.constructor == y.constructor) // true
console.log(
x.map(function() { return 0; })
)
// [undefined, undefined, undefined]
console.log(
y.map(function() { return 0; })
)
// [0, 0, 0]
What's going on here? Is this a bug, or am I misunderstanding how to use new Array(3)?
I had a task that I only knew the length of the array and needed to transform the items.
I wanted to do something like this:
let arr = new Array(10).map((val,idx) => idx);
To quickly create an array like this:
[0,1,2,3,4,5,6,7,8,9]
But it didn't work because:
(see Jonathan Lonowski's answer)
The solution could be to fill up the array items with any value (even with undefined) using Array.prototype.fill()
let arr = new Array(10).fill(undefined).map((val,idx) => idx);
console.log(new Array(10).fill(undefined).map((val, idx) => idx));
Update
Another solution could be:
let arr = Array.apply(null, Array(10)).map((val, idx) => idx);
console.log(Array.apply(null, Array(10)).map((val, idx) => idx));
It appears that the first example
x = new Array(3);
Creates an array with a length of 3 but without any elements, so the indices [0], [1] and [2] is not created.
And the second creates an array with the 3 undefined objects, in this case the indices/properties them self are created but the objects they refer to are undefined.
y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];
As map runs on the list of indices/properties, not on the set length, so if no indices/properties is created, it will not run.
With ES6, you can do [...Array(10)].map((a, b) => a) , quick and easy!
From the MDC page for map:
[...] callback is invoked only for indexes of the array which have assigned value; [...]
[undefined] actually applies the setter on the index(es) so that map will iterate, whereas new Array(1) just initializes the index(es) with a default value of undefined so map skips it.
I believe this is the same for all iteration methods.
ES6 solution:
[...Array(10)]
Doesn't work on typescript (2.3), though
The arrays are different. The difference is that new Array(3) creates an array with a length of three but no properties, while [undefined, undefined, undefined] creates an array with a length of three and three properties called "0", "1" and "2", each with a value of undefined. You can see the difference using the in operator:
"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true
This stems from the slightly confusing fact that if you try to get the value of a non-existent property of any native object in JavaScript, it returns undefined (rather than throwing an error, as happens when you try to refer to a non-existent variable), which is the same as what you get if the property has previously been explictly set to undefined.
For reasons thoroughly explained in other answers, Array(n).map doesn't work. However, in ES2015 Array.from accepts a map function:
let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5
let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10
In ECMAScript 6th edition specification.
new Array(3) only define property length and do not define index properties like {length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Step 9.
[undefined, undefined, undefined] will define index properties and length property like {0: undefined, 1: undefined, 2: undefined, length: 3}. see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Step 5.
methods map, every, some, forEach, slice, reduce, reduceRight, filter of Array will check the index property by HasProperty internal method, so new Array(3).map(v => 1) will not invoke the callback.
for more detail, see https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map
How to fix?
let a = new Array(3);
a.join('.').split('.').map(v => 1);
let a = new Array(3);
a.fill(1);
let a = new Array(3);
a.fill(undefined).map(v => 1);
let a = new Array(3);
[...a].map(v => 1);
I think the best way to explain this is to look at the way that Chrome handles it.
>>> x = new Array(3)
[]
>>> x.length
3
So what is actually happening is that new Array() is returning an empty array that has a length of 3, but no values. Therefore, when you run x.map on a technically empty array, there is nothing to be set.
Firefox just 'fills in' those empty slots with undefined even though it has no values.
I don't think this is explicitly a bug, just a poor way of representing what is going on. I suppose Chrome's is "more correct" because it shows that there isn't actually anything in the array.
Not a bug. That's how the Array constructor is defined to work.
From MDC:
When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array. The following code creates an array of five elements:
var billingMethod = new Array(5);
The behavior of the Array constructor depends on whether the single parameter is a number.
The .map() method only includes in the iteration elements of the array that have explicitly had values assigned. Even an explicit assignment of undefined will cause a value to be considered eligible for inclusion in the iteration. That seems odd, but it's essentially the difference between an explicit undefined property on an object and a missing property:
var x = { }, y = { z: undefined };
if (x.z === y.z) // true
The object x does not have a property called "z", and the object y does. However, in both cases it appears that the "value" of the property is undefined. In an array, the situation is similar: the value of length does implicitly perform a value assignment to all the elements from zero through length - 1. The .map() function therefore won't do anything (won't call the callback) when called on an array newly constructed with the Array constructor and a numeric argument.
Just ran into this. It sure would be convenient to be able to use Array(n).map.
Array(3) yields roughly {length: 3}
[undefined, undefined, undefined] creates the numbered properties:
{0: undefined, 1: undefined, 2: undefined, length: 3}.
The map() implementation only acts on defined properties.
If you are doing this in order to easily fill up an array with values, can't use fill for browser support reasons and really don't want to do a for-loop, you can also do x = new Array(3).join(".").split(".").map(... which will give you an array of empty strings.
Quite ugly I have to say, but at least the problem and intention are quite clearly communicated.
Since the question is why, this has to do with how JS was designed.
There are 2 main reasons I can think of to explain this behavior:
Performance: Given x = 10000 and new Array(x) it is wise for the constructor to avoid looping from 0 to 10000 to fill the array with undefined values.
Implicitly "undefined": Give a = [undefined, undefined] and b = new Array(2), a[1] and b[1] will both return undefined, but a[8] and b[8] will also return undefined even if they're out of range.
Ultimately, the notation empty x 3 is a shortcut to avoid setting and displaying a long list of undefined values that are undefined anyway because they are not declared explicitly.
Note: Given array a = [0] and a[9] = 9, console.log(a) will return (10) [0, empty x 8, 9], filling the gap automatically by returning the difference between the two values declared explicitly.
Here's a simple utility method as a workaround:
Simple mapFor
function mapFor(toExclusive, callback) {
callback = callback || function(){};
var arr = [];
for (var i = 0; i < toExclusive; i++) {
arr.push(callback(i));
}
return arr;
};
var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Complete Example
Here's a more complete example (with sanity checks) which also allows specifying an optional starting index:
function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
from = arguments[0];
toExclusive = arguments[1];
callback = arguments[2];
} else if (arguments.length == 2) {
if (typeof arguments[1] === 'function') {
from = 0;
toExclusive = arguments[0];
callback = arguments[1];
} else {
from = arguments[0];
toExclusive = arguments[1];
}
} else if (arguments.length == 1) {
from = 0;
toExclusive = arguments[0];
}
callback = callback || function () {};
var arr = [];
for (; from < toExclusive; from++) {
arr.push(callback(from));
}
return arr;
}
var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]
Counting Down
Manipulating the index passed to the callback allows counting backwards:
var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
return count - 1 - i;
});
// arr = [2, 1, 0]
Why does the first console.log() print undefined values but second one has transformed values? I know it has to do something with function scope but not getting it
var array = [1,2,3,4,5,7];
function incrementByOne(arr) {
arr = arr.map(function(value, index, array){
arr[index] = arr[index] +1;
});
console.log(arr);
}
incrementByOne(array);
console.log(array);
// [undefined, undefined, undefined, undefined, undefined, undefined]
// [2, 3, 4, 5, 6, 8]
also i notice that the first console.log() knows how many times to iterate but what happens to the value...
js bin link
you need to return the incremented value from the function inside map. use return arr[index] +1
Also you need to return the new array formed using map and stored in arr now.
var array = [1,2,3,4,5,7];
function incrementByOne(arr) { //contains array reference
arr = arr.map(function(value, index, array){
return value +1;
});
//now arr contains a new array and doesn't refer to passed array anymore.
console.log(arr);
return arr;
}
array = incrementByOne(array);
console.log(array);
// [undefined, undefined, undefined, undefined, undefined, undefined]
// [2, 3, 4, 5, 6, 8]
if you don't want to return, you can use forEach(), as in that case arr will refer to passed array throughout. The difference is because map returns a new array.
var array = [1,2,3,4,5,7];
function incrementByOne(arr) { //contains array reference
arr.forEach(function(value, index){
arr[index] = value +1;
});
//arr still refers to the passed array.
console.log(arr);
}
incrementByOne(array);
console.log(array);
// [undefined, undefined, undefined, undefined, undefined, undefined]
// [2, 3, 4, 5, 6, 8]
Well, all the above answers are correct but they miss the most important point here. There is a concept in JavaScript called call by sharing.
Consider this code:
var num= 3;
var json = {myValue : '10'};
var json2 = {myValue : '100'};
function callBySharing(a,b,c){
a = a + 37;
b = {myValue : 'new value'};
c.myValue = 'new Value';
}
callBySharing(num,json,json2);
console.log(num);//3 *UNCHANGED*
console.log(json.myValue);//10 *UNCHANGED*
console.log(json2.myValue);//'new Value' *CHANGED*
So what you are doing is same as what is happening in json.myvalue; You are trying to update the whole object and replace it with the new value. So a very simple change in the code with do this for you:
var array = [1,2,3,4,5,7];
function incrementByOne(arr) {
arr.map(function(value, index, array){
arr[index] = arr[index] +1;
});
console.log(arr);
}
incrementByOne(array);
console.log(array);
I just replaced the arr= arr.map().... part to just arr.map().....
What this does is, it changes the function to json2.myValue example case.
So what is the difference between the 2: JS lets you update items within the object but not the whole object.By making the above said change in code, you are updating individual values of arr and not replacing the whole object with new values. I learnt this concept from SO only back when I was confused with it. So I am linking the post(Is JavaScript a pass-by-reference or pass-by-value language?)
Hope this helps!
var array = [1,2,3,4,5,7];
function incrementByOne(arr) {
arr = arr.map(v => v + 1);
console.log(arr);
}
incrementByOne(array);
console.log(array);
// [undefined, undefined, undefined, undefined, undefined, undefined]
// [2, 3, 4, 5, 6, 8]
A couple of things going on here:
The callback you pass into the .map function should return a value with the return keyword. If you don't return a value from the callback, the default return value is undefined. That's why the 'arr' array you define in incrementByOne only has undefined values inside of it when you log it with your first console.log. I think what you're really trying to do is simply return value + 1 inside of the callback.
You're making a classic passed-by-value passed-by-reference error here; it's a mistake everyone makes when they're first learning JS. I'd recommend checking out posts like this one. In short, inside of your .map callback you are mutating the array that you passed into incrementByOne, which is the same array you log out with the second console.log; that's why the values appear to have been incremented correctly.
As I'm learning the forEach() method in JavaScript, I have several questions related to it.
Currently I've written the following piece of code:
var arr = [1,2,3,4,5,6];
arr.forEach(function(elem, idx, arr){
elem=elem*2;
});
alert(arr); // "1,2,3,4,5,6"
My goal is to simply multiply each element in arr by 2, however when I use alert to examine the final values it seems that values inside arr hasn't been modified. What's the problem here?
Also I'm a little confused about the 3 arguments that forEach's function takes. First, is it required to pass in 3 arguments? What will happen if one doesn't provide exactly 3 arguments? Some tutorials I've looked at seem to provide only 1 argument, yet the explanation wasn't clear. Second, do the names of arguments matter (e.g. e, elem, or element)?
Thank you.
Your approach
You are not assigning new value to anything else but the elem, which is only in callback's scope. Modify your code, so that the elem * 2 is assigned to arr[idx]. Working example:
var arr = [1, 2, 3, 4, 5, 6];
arr.forEach(function(elem, idx) {
arr[idx] = elem * 2;
});
document.body.textContent = arr;
Better approach
For tasks like that however, you should use map:
var arr = [1, 2, 3, 4, 5, 6];
arr = arr.map(function(num) {
return num * 2;
});
document.body.textContent = arr;
The array is not getting modified cus you are not modifying it. To do that update the code as following.
var arr = [1,2,3,4,5,6];
arr.forEach(function(elem, idx, arr){
arr[idx] = elem*2
});
console.log(arr);
If you check the console you would see the updated arr.
you cannot edit the array in place by returning the new value or changing elem. You can however use the idx variable like arr[idx] = elem * 3 or use the map function.
you can omit the parameters that you don't need
you can name them whatever you want
For first problem, just use map
var arr = [1,2,3,4,5,6];
arr = arr.map(function(elem){
return elem*2;
});
alert(arr); // "2,4,6,8,10,12"
arguments is optional, and names doesn't matter at all.
The arguments are optional.
idx lets you know the index of the array element you are currently evaluating.
arr, is the array you are evaluating.
so elem == arr[idx]
I'm reading a book called Eloquent JavaScript. There's an exercise in it that requires one to flatten a heterogeneous array & after trying so long and failing to get the answer, I looked up the solution online & couldn't understand the code. I'm hoping someone will be kind enough to explain, especially for argument "flat" and how it's supposed to work. The code is below
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));
The reduce function defined in the book is:
function reduce(array, combine, start) {
var current = start;
for (var i = 0; i < array.length; i++)
current = combine(current, array[i]);
return current;
}
and as a method of an array,
arr.reduce(combine, start);
Let's look at each part of the reduce method. The book describes it as "folding up the array, one element at a time." The first argument for reduce is the "combiner function", that accepts two arguments, the "current" value and the "next" item in the array.
Now, the initial "current" value is given as the second argument of the reduce function, and in the solution of flattening arrays, it is the empty array, []. Note that in the beginning, the "next" item in the array is the 0th item.
Quoting the book to observe: "If your array contains at least one element, you are allowed to leave off the start argument."
It may also be confusing that in the flattening solution, current is placed as the second argument to reduce, whereas in the reduce definition above, current is used to assign the cumulative, folded value. In the flattening solution, current refers to the "next" arrays item (the individual array of integers)
Now, at each step of the reduction, the "current" value plus the next array item is fed to the (anonymous) combiner, and the return value becomes the updated "current" value. That is, we consumed an element of the array and continue with the next item.
flat is merely the name given to the accumulated result. Because we wish to return a flat array, it is an appropriate name. Because an array has the concat function, the first step of the reduce function is, (pretending that I can assign the internal variables)
flat = []; // (assignment by being the second argument to reduce)
Now, walk through the reduction as iterating over arrays, by going through the steps shown above in reduce's definition
for (var i = 0; i < arrays.length; i++)
flat = combine(flat, arrays[i]);
Calling combine gives [].concat([1, 2, 3]) // => [1, 2, 3]
Then,
flat = [1, 2, 3].concat([4, 5]) // => [1, 2, 3, 4, 5]
and again for the next iteration of the reduction. The final return value of the reduce function is then the final value of flat.
This would be the solution I came with with ES6 format:
const reduced = arrays.reduce((result,array) => result.concat(array),[]);
console.log(reduced);
I have implemented this solution and this seems to work for nested arrays as well.
function flattenArray(arr){
for(var i=0;i<arr.length;i++){
if(arr[i] instanceof Array){
Array.prototype.splice.apply(arr,[i,1].concat(arr[i]))
}
}
return arr;
}
There is an easy way to do these exercises. those functions are already built inside the javascript so you can use them easily.
But the whole joy of this exercise is to create those functions:
Create reduce function. Reduce function should add all array elements. you can use a higher-order function or just a normal one. here is an example for higher-order:
function reduce(array, calculate){
let sumOfElements = 0;
for(let element of array){
sumOfElements = calculate(sumOfElements, element)
}
return sumOfElements
}
Next step is to create a concat function. since we need to return those reduced arrays in new array we will just return them. (Warning: you must use rest parameter)
function concat(...arr){
return arr
}
And for last. you will just display it (You can use any example)
console.log(concat(reduce([1, 2, 3, 4], (a, b) => a + b), reduce([5, 6], (a, b) => a + b)))
The reduce method acts as a for loop iterating over each element in an array. The solution takes each array element and concatenates it to the next one. That should flatten the array.
var arr =[[1,2],[3,4],[5,6]]
function flatten(arr){
const flat= arr.reduce((accumulator,currentValue)=>{
return accumulator.concat(currentValue)
})
return flat
}
console.log(flatten(arr))
//Output 1,2,3,4,5,6
For the life of me, I just can't figure out what I'm doing wrong here.
I'm trying to use both the reduce and concat array methods to take all of the values of a 2d array and combine them into a single value (basically condense them into a single array and then sum them up).
The problem that I keep running into is that when I try to make a for/loop to concat each array element, the argument that I'm passing into the function is not being recognized as an array, thus my call to .concat() is failing. I've placed a console.log() at the beginning of the function to see if the element is being recognized as the first array element in the 2d array, and it's coming up as "1"(?).
I tried another test outside of the function, and it logs as the actual array element. What am I doing wrong here? code below:
var arrays = [[1, 2, 3], [4, 5], [6]];
var myArray = arrays[0]; // Test
console.log(myArray); // Test
var flatArray = arrays.reduce(function(arrays)
{
console.log(arrays[0]); // Test
for (var i = 0; i < arrays.length - 1; i++)
{
arrays[0].concat(arrays[i+1]);
}
return arrays;
});
console.log(flatArray);
This is the output that I keep getting:
Array [ 1, 2, 3 ]
1
TypeError: arrays[0].concat is not a function
It's almost seems like array is being converted to a number-type when inside the function...?
You have an error in your code here:
var flatArray = arrays.reduce(function(param) {})
that param will be an element of your arrays vector.
Check this https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
You are using .reduce() incorrectly and you don't even need to use it to flatten an array. You can just do this:
var flatArray = [].concat.apply([],arrays);
Working demo: http://jsfiddle.net/jfriend00/wfjyfp42/
To understand .reduce(), the callback you pass it gets four arguments (see MDN reference). The first two arguments are important in using .reduce() correctly:
callback(previousValue, currentValue, index, array)
The previousValue is the accumulated value so far in the reduction. The currentValue is the next element of the array that is being iterated. The last two arguments do not need to be used if not needed.
Your code is only using the previousValue so it is never looking at the next item in the array as passed in by .reduce().
You could make a solution work using .reduce() like this:
var flatArray = arrays.reduce(function(previousValue, currentValue) {
return previousValue.concat(currentValue);
}, []);
Working demo: http://jsfiddle.net/jfriend00/2doohfc5/
Reduce performs an operation on two elements.
var sum = [[1, 2, 3], [4, 5], [6]].reduce(function(a, b) {
return a.concat(b);
}).reduce(function(a, b) {
return a + b;
});