Doesn't the ES6 spread operator flatten an array? - javascript

Not react specific, so hope ok to ask, but I thought the spread operator flattens an array?
So with the following sum function which sums the args, you can use .apply to pass in the values:
function sum() {
return arguments.reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.apply(null, values));
I thought you could just addat the function and use the spread operator to flatten the array so call could be used. (I know you wouldn't use call in this instance, but I was just surprised as I thought the spread flattened the array:
function sum() {
return [...arguments].reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.call(null, values));
This returns the string 02,4,8,12,16

This happens because arguments is array-like of array. The array becomes converted to string.
As call documentation says,
While the syntax of this function is almost identical to that of apply(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.
This should work as expected with:
sum(...values);
sum.call(null, ...values);
sum.apply(null, values);
By the way, arguments and call aren't welcome in ES6.

The arguments is an array like structure so while passing the array as an argument the structure would be like a nested array. So applying spread operator results an array in the format [[2, 4, 8, 12, 16]](i.e, [...[[2, 4, 8, 12, 16]]]) and the reduce method apply 0 + [2, 4, 8, 12, 16] and which results "02,4,8,12,16".
To make it work you need to pass the array values as arguments using Function#apply or get the first argument.
function sum() {
return [...arguments].reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.apply(null, values));
function sum() {
return [...arguments][0].reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.call(null, values));
Refer : What is the difference between call and apply?

Related

map() method mutating the calling Array

map() can't mutate the calling array, instead it returns a new Array with modified values.
But, the following code mutating the original Array, is there any wrong in my understanding?
const arr = [1, 2, 3, 4, 5];
arr.map((num, index, arr1) => {
return arr1[index] = num * 2;
});
console.log(arr); // [2, 4, 6, 8, 10]
Well, you're mutating the original array by passing its reference into the callback function inside map() (arr1) and then manually accessing the indices. It will create a new array if you just return the value from that function.
const arr = [1, 2, 3, 4, 5];
const arr1 = arr.map((num) => {
return num * 2;
});
console.log(arr); // [1, 2, 3, 4, 5]
console.log(arr1); // [2, 4, 6, 8, 10]
The third argument to the callback function of map is the
original/source array on which the map is called upon
The arr and arr1 are both same i.e both are referencing on the same array, You can see it by using console.log(arr === arr1). So what ever you operation perform on the arr1, it gonna affect the arr.
const arr = [1, 2, 3, 4, 5];
arr.map((num, index, arr1) => {
console.log(arr1 === arr);
return num * 2;
});
You can just return num * 2 from the callback function. map internally creates a new array and return it. So you don't have to assign it as
arr1[index] = num * 2
You can also make it one-liner as:
arr.map((num, index, arr1) => num * 2)
const arr = [1, 2, 3, 4, 5];
const result = arr.map((num, index, arr1) => {
return num * 2;
});
console.log(arr); // [2, 4, 6, 8, 10]
console.log(result); // [2, 4, 6, 8, 10]
Array.map creates a new array populated with the results of calling a provided function on every element in the calling array.
Here its specifed that you must call or execute a function on every element of calling array.
What is the issue with your code?
You are not actually calling a function, you are instead updating the original array. If you are looking to create a new array by multiplying each node of the element with 2, you should do something like below.
Working Example
const arr = [1, 2, 3, 4, 5];
const newArray = arr.map((nodeFromOriginalArray, indexOfCurrentElement, arrayFromMapCalled) => {
return nodeFromOriginalArray * 2;
});
console.log(arr);
console.log(newArray);
Lets debug the paremeters inside the map function.
Here we have provided three arguments.
First argument nodeFromOriginalArray: The current element being processed in the array. This will be each node from your calling array.
Second argument indexOfCurrentElement: The index of the current element being processed in the array. Which means, the index of current element in calling array.
Third argument arrayFromMapCalled: The array map was called upon. This is the array on which the map function is getting executed. Please note, this is the original array. Updating properties inside this array results in updating your calling array. This is what happened in your case.
You should not modify your original array, which is the third parameter. Instead, you should return your node multipled by 2 inside map and assign this to a new array. Updating the third paramater inside the map function will mutate your calling array.
When calling map on an array, you provide a mapper with three arguments, an item in the array, it's index and the array itself (as you've represented in your snippet).
map takes the value returned by the function mapper as the element at the index in a new array returned by the operation.
const arr = [1,2,3,4,5]
const doubled = arr.map(x => x * 2) // [2,4,6,8, 10]
A over simplified implementation of map (without the index and originalArray params) might look like this. Let's assume that instead of being a method on the array instance, it's a function that takes an array and a mapper function.
I would not recommend re-implementing in production code, there's the native implementation as well as several libraries such as lodash and underscore that implement it.
function map(arr, mapper) {
const result = [];
for (const item of arr) {
const resultItem = mapper(item);
result.push(resultItem);
}
return result;
}
function double(x) {
return x * 2;
}
const doubled = map([1,2,3,4,5,6], double); // [2, 4, 6, 8 ,10, 12]

how does this concat command work with this array?

I came across this JS problem but I can't figure out the syntax of how it's working, could someone please help to explain? I don't understand the empty square bracket syntax at the start and then how the concat is being applied with another empty square bracket? it's just quite confusing for me.
Appreciate any help to step through this.
let arr = [[1, 2],[3, 4],[5, 6, 7, 8, 9],[10, 11, 12]];
let flattened = [].concat.apply([], arr);
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]
let arr = [[1, 2],[3, 4],[5, 6, 7, 8, 9],[10, 11, 12]];
let flattened = [].concat.apply([], arr);
*first array*---| |---*second array*
The first array has one goal:
to invoke concat() method with this. Here this is the second array. Then the first array is thrown away
Then apply() method flattens the arr array to one level.
You can check it:
let foo = [0].concat([1,2,3], [[1,2,3]] );
console.log(foo)
UPDATE:
In addition, the first array can be removed:
const arr = [[1, 2],[3, 4],[5, 6, 7, 8, 9],[10, 11, 12]];
const flattened = Array.prototype.concat.apply([], arr);
console.log(flattened)
Some info how apply() works.
In addition, it would be really useful to know how apply() flattens array items to one level or how apply() takes an array of arguments and treats each element of that array as a single argument.
Let me show a simple example. We have the following object and method:
const showFullName = (arg_1, arg_2) => {
console.log(`arg_1 is ${arg_1}`);
console.log(`arg_2 is ${arg_2}`);
}
let foo = {};
showFullName.apply(foo, ['firstName', 'surname']);
The [] is an empty array. The concat function concatenate two or more arrays: [].concat(arr[0], arr[1], arr[2], arr[3])
The catch here is to know how apply works. The second parameter of apply is an array of parameters. It's like doing this (if you're familiar with the concept of desctructuring):
[].concat(...arr)
You can read more about apply here

sort() does not work

I use sort() to sort my table but I do not understand why it does not work. you have an idea ?
var tab = [5, 15, 17, 3, 8, 11, 28, 6, 55, 7];
tab = tab.sort();
for (var i = 0; i < tab.length; i++) {
$("p").append(" ", tab[i]);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p></p>
https://jsfiddle.net/1423tLwd/
By default the sort method will sort the array that it has been called on alphabetically.
To get around this you need to pass sort a callback function that will sort the elements by their numerical value.
To achieve this you need to do the following:
function sortNumber(a, b) {
return a - b;
}
let tab = [5, 15, 17, 3, 8, 11, 28, 6, 55, 7];
let sortedTab = tab.sort(sortNumber);
console.log(sortedTab);
As Explained in MDN web docs:
The default sort order is according to string Unicode code points.
That is, you should give sort the function that is to compare elements of the array, otherwise, your array will be sorted according to string Unicode code points.
This should work (sort in ascending order):
function compareFunction(a, b) {
return a - b;
}
// sort tab array using compareFunction to compare elements
tab.sort(compareFunction);

what does max() function do in javascript if array has several equally large numbers

If we get something like
array=[5,5,5,5,3,2];
return Math.max.Apply(Math,array);
How do I get it to return the numbers from first to last if such a case occurs.
To answer the question in the title:
what does max() function do in javascript if array has several equally
large numbers
The answer is, nothing. Math.max() doesn't act on arrays.
You can pass an array by spreading the items as arguments to max():
Math.max(...[1,2,3]) // 3
Or as you've seen, with apply():
Math.max.apply(Math, [1,2,3]) // 3
If the question is more:
What does Math.max() do when more than one of the same maximum number is given?
The answer is, it returns that number:
const a = [5, 5, 5, 5, 3, 2]
const max = Math.max(...a)
console.log(max) // 5
This question is confusing:
How do I get it to return the numbers from first to last if such a case occurs.
You want it to return a sorted array? From [5, 5, 5, 5, 3, 2] to [2, 3, 5, 5, 5, 5]?
a.sort() // [2, 3, 5, 5, 5, 5]
You want dupes removed? From [5, 5, 5, 5, 3, 2] to [2, 3, 5]?
Array.from(new Set(a)) // [2, 3, 5]
Could you clarify your question?
The best way to do this is the following:
var a = [5,5,5,5,3,2];
var largest = Math.max.apply(null,a)
var filtered = a.filter(function(item) {
item === largest
});
Where filtered will have contain all the largest elements.
In #Clarkie's example, he's calling Math.max more frequently than needed.
In both Dan and Clarkie's example they're capitalizing Apply which is incorrect, the correct function to call is Math.max.apply and Math need not be passed in as the first argument.
See the following for a working example:
https://jsfiddle.net/fx5ut2mm/
Modifying #Clarkie's very nice idea. We can boil it down to...
var a = [5,5,5,5,3,2],
m = Math.max(...a),
f = a.filter(e => e == m);
document.write("<pre>" + JSON.stringify(f) + "</pre>");

JavaScript Map Reduce: weird behavior

var t = [-12, 57, 22, 12, -120, -3];
t.map(Math.abs).reduce(function(current, previousResult) {
return Math.min(current, previousResult);
}); // returns 3
t.map(Math.abs).reduce(Math.min); // returns NaN
I don't understand why the second form doesn't work. Any explanations are welcomed.
EDIT: Technical context: Chrome and Firefox JavaScript engine. See ES5 reduce http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21
Math.min accepts multiple arguments. This is exactly the same reason this doesn't work for parseInt or other functions like that. you need to bind the parameters yourself.
reduce feeds the values like index and array to Math.min
We can confirm this if we follow the following steps:
First, we proxy Math.min:
var oldMath = Math.min;
Math.min = function (){
console.log(arguments)
return oldMath.apply(Math, arguments);
}
Then we run the second version:
[-12, 57, 22, 12, -120, -3].reduce(Math.min);
Which logs:
[-12, 57, 1, Array[6]]
Since Array[6] is not a number, the result is NaN
Here is a very similar example from MDN:
["1", "2", "3"].map(parseInt);
While one could expect [1, 2, 3]
The actual result is [1, NaN, NaN]
parseInt is often used with one argument, but takes two. The second being the radix
To the callback function, Array.prototype.map passes 3 arguments: the element, the index, the array
The third argument is ignored by parseInt, but not the second one, hence the possible confusion.
reduce's callback is called passing four arguments: previousValue, currentValue, index and array. And because Math.min is a variadic function, your code:
t.map(Math.abs).reduce(Math.min); // returns NaN
is equivalent to:
t.map(Math.abs).reduce(function(current, previousResult, index, array) {
return Math.min(current, previousResult, index, array);
});
That's why the result is NaN: the last parameter, array, is not a number.
You can also solve this kind of issue with a high-ordered function like this one:
function binary (fn) {
return function (a, b) {
return fn.call(this, a, b);
}
}
And then:
t.map(Math.abs).reduce(binary(Math.min));
will works.

Categories

Resources