How does join() produce different results depending on the arguments? - javascript

I can't quite understand why the join() call below produces different results, depending on the type of argument(s) provided.
Here's what I found:
var test = function() {
var args = Array.prototype.join.call(arguments,"_");
return args
};
console.log(test([1,2,3])) // #1: returns 1,2,3
console.log(test(1,2,3)) // #2: returns 1_2_3
Given join(arguments, '_'), shouldn't it produce a _ delimited string in both tests above? Why does #1 return a comma delimited value instead?

When you pass an array to test, the arguments object becomes an array of arrays, not a plain array of plain values. It's the difference between
arguments = [[1,2,3]];
and
arguments = [1,2,3];
When you call .join on an array of arrays, each inner array gets implicitly coerced to a string first, resulting in '1,2,3' - the values of the inner arrays do not get .joined by the delimiter, only the immediate children of the outer array get .joined by the delimiter.

In your code, you only have one argument in the first example, that being an array. Joining a single element will remove the brackets:
var test = function() {
var args = Array.prototype.join.call(arguments,"_");
return args
};
console.log(test([1,2,3])) // args = [[1,2,3]]
console.log(test(1,2,3)) // args = [1,2,3]
console.log([[1,2,3]].join('_'))
console.log([1,2,3].join('_'))
Another way to look at this is to provide another array as an argument to test():
var test = function() {
var args = Array.prototype.join.call(arguments,"_");
return args
};
console.log(test([1,2,3], [4,5,6]))

In the first example you are not calling .join on the array but on arguments. That variable will be populated with an array-like object that has an array as first index, in essence, you are calling the equivalent of:
let myArguments = [[1, 2, 3]];
console.log(myArguments.join("_"));
instead of what you do (the equivalent of) in the second example:
let myArguments = [1, 2, 3];
console.log(myArguments.join("_"));

The first one, first argument is [1,2,3] which is joined with the second argument, which is nothing -> output is [1,2,3].toString().
Second call, it's actually joining all the 3 arguments, resulting in outputting 1_2_3.

Because you're passing one argument which is an array - so it converts it to a string, then attempts to join it to the other arguments (there are none) so it just returns the string.
var test = function() {
var args = Array.prototype.join.call(arguments, "_");
return args
};
console.log(test([1, 2, 3]));
console.log(test(1, 2, 3));
You can solve this by checking if there's only one argument passed.
var test = function() {
var args = arguments.length == 1 ? arguments[0].join("_") : Array.prototype.join.call(arguments, "_");
return args;
};
console.log(test([1, 2, 3]));
console.log(test(1, 2, 3));

The result is different, because arguments is different.
On the first call (test([1,2,3]), you have one argument which is an array.
On the second call, you have 3 arguments, each one being a number.
Array.prototype.join is meant to be called over arrays. It will stringify each of the items in the array.
In you your first case, your arguments "array" has only one member, which is an array itself. This argument will be stringfied. An array stringfied becomes exactly what is logged in your code.

Related

What the point of spread operaror in JavaScript especially on this example?

I am studying now at FreeCodeCamp, and here is a challenge:
"We have defined a function, copyMachine which takes arr (an array)
and num (a number) as arguments. The function is supposed to return a
new array made up of num copies of arr. We have done most of the work
for you, but it doesn't work quite right yet. Modify the function
using spread syntax so that it works correctly (hint: another method
we have already covered might come in handy here!)."
And here is a solution:
function copyMachine(arr, num) {
let newArr = [];
while (num >= 1) {
// Only change code below this line
newArr.push([...arr])
// Only change code above this line
num--;
}
return newArr;
}
console.log(copyMachine([true, false, true], 2));
What the point to add extra brackets and dots (...) if i could just add newArr.push(arr) instead and get the same result?
The result is: [ [ true, false, true ], [ true, false, true ] ]
It's the matter of reference.
When using this syntax newArr.push(arr), you're pushing the original array from the argument, so whenever the arr changes its content, arrays inside newArr will also update since it is always the same one array.
When using spread syntax, you're actually pushing a copy of that arr. This mean it's a new array that is not tied to the array you pass to a function
Consider this
function copyMachine(arr, num) {
let newArr = [];
while (num >= 1) {
// Only change code below this line
newArr.push(arr)
// Only change code above this line
num--;
}
return newArr;
}
const oldArr = [true, false, true];
const newArr = copyMachine(oldArr, 2);
oldArr.push(true);
console.log(newArr); // contains an element added to the old array
Spread syntax (...) allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
In the provided code the spread operator is used to make a new array using the elements of the array named arr and then push it onto newArr.
This is similar to doing newArr.push(arr);

Iterating inside JavaScript filter method

I am trying to compare two given parameters of a function. The exact problem is as follows:
You will be provided with an initial array (the first argument in the destroyer function), followed by one or more arguments. Remove all elements from the initial array that are of the same value as these arguments.
Note
You have to use the arguments object.
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3)); // expected output: [1,1]
I am using filter method to iterate over the array but I couldn't compare the args with the elements of the array inside the callback of the filter.
function destroyer(arr, ...args) {
let result = arr.filter(num => {
for (let i = 0; i<=args.length; i++ ){
num !== args[i]
}
});
return result;
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
I can iterate with for loop but I cannot use the output of for loop to do filter iteration.
Any ideas?
Probably an easier way to achieve the goal using .filter() with .includes(). Additionally you can use ...rest so called rest parameters for you function, see form the documentation:
The rest parameter syntax allows us to represent an indefinite number of arguments as an array.
Try as the following:
const destroyer = (arr, ...rest) => {
return arr.filter(num => !rest.includes(num));
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
I hope this helps!
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
Example:
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]
via MDN
Filter iterates over all elements of some array and returns a new array. It puts an element in the new array only if callback (your function invoked as a parameter of filter) return true otherwise it's omitted.
Next it's worth to use rest parameters to achieve two arrays (initial and values to exclude).
The rest parameter syntax allows us to represent an indefinite number of arguments as an array.
function sum(...theArgs) {
return theArgs.reduce((previous, current) => {
return previous + current;
});
}
console.log(sum(1, 2, 3));
// expected output: 6
console.log(sum(1, 2, 3, 4));
// expected output: 10
Solution with explanation:
//Declare your function, first parameter is initial array, the second one is also array created by using rest parameters
function destroyer(initialArray = [], ...toExclude) {
// filter initialArray, if el (single element) is NOT included in "toExclude" it returns true
// and add this particular element to the result array
let result = initialArray.filter(el => toExclude.includes(el) == false);
//return result
return result;
}

Arguments for the Seek and Destroy Freecode camp challenge

I'm trying to understand the Seek and Destroy challenge below.
Task: You will be provided with an initial array (the first argument in the destroyer function), followed by one or more arguments. Remove all elements from the initial array that are of the same value as these arguments.
This is the initial code below:
function destroyer(arr) {
// Remove all the values
return arr;
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);
After several (really) several attempts and looking at other people's code, I was able to resolve the task. However, I think it was more out of luck. I’ve copied my code below, but I was hoping someone could clarify a couple of things for me.
The code below passes whether I return val or args within the iterateThroughArray function. Why is that?
If I am supposed to be comparing ALL arguments against the first argument, where in this code am I indicating that? I kept thinking I needed to splice the first argument to compare all other arguments against it or create a variable for argument[0]. Any guidance you can provide is greatly appreciated!
function destroyer(arr) {
var args = Array.from(arguments); //this also converts them to an array
var iterateThroughArr = function (val) {
if (args.indexOf(val) ===-1){
return args;
}
};
return arr.filter(iterateThroughArr);
}
This may sound a lot to take in but here is my explanation
function destroyer(arr) {
var args = Array.from(arguments); //Here arr is converted to [Array(6),2,3]
//console.log(args)
/* var iterateThroughArr = function (val) {
if (args.indexOf(val) ===-1){
return args;
}
};
return arr.filter(iterateThroughArr);
*/
// to make more clear the above code can be rewritten as below
var arr = arr.filter(function (val) {
console.log("args = "+ args + " val = " + val + " indexOf(val) " + args.indexOf(val) )
// here you are itterating through each arr val which in this case is[1,2,3,1,2,3]
// if you dont believe me uncomment the next console.log() and see the output
// console.log(val)
if (args.indexOf(val) ===-1){
// here is where the magic happens
// Now you are checking if val exisists by using a builtin method called .indexOf()
// In general, .indexOf() returns -1 if a value does not exist within an array
//Below is working example
/* var array = [1,2,3,4,5]
console.log(array.indexOf(1)) // 0 since it is located at index 0
console.log(array.indexOf(5)) // 4 since it is located at index 4
console.log(array.indexOf(10)) // -1 since it does not exisit
*/
// Therefore, if value passes the above if statement
//then that means it doesnot exisit on args([Array(6),2,3])
//which as a result will be included on the filtered array
return args;
}
});
return arr;
}
var val = destroyer([1, 2, 3, 1, 2, 3], 2, 3);
//console.log(val)
Basically, what you need to understand is how filter works and how .indexOf works.
For more detail visit Mozilla documentation: .indexOf() and .filter()
Give this a try:
function destroyer(arr) {
// arr1 is equal to the array inside arr
var arr1 = arr.slice(arguments);
// arr2 becomes an array with the arguments 2 & 3
var arr2 = Array.prototype.slice.call(arguments, 1);
// this function compares the two and returns an array with elements not equal to the arguments
return arr1.concat(arr2).filter(function (item) {
return !arr1.includes(item) || !arr2.includes(item)
})
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);

What is happening when adding a number to the array element

Example 1:
Var arr = [1,2,3] + 1;
console.log(arr) // "1,2,31"
typeof arr // String
Example 2:
var arr = 5 + ["h","e","l","l","o"];
console.log(arr) // "5h,e,l,l,o";
typeof arr // String
Doing the above concatenate the number/string to the last/first element in the array. Would like to understand what happens here?
While you are using + (concatenation in this case ), it's applying toString() method to the array. So it's just concatenating with the result and not appending with last or first element.
The Array object overrides the toString method of Object. For Array objects, the toString method joins the array and returns one string containing each array element separated by commas.
JavaScript calls the toString method automatically when an array is to be represented as a text value or when an array is referred to in a string concatenation. (Taken from here)
Check the following snippet all those are results the same.
var arr = [1, 2, 3];
// all these are providing the same result
// result of toString method
console.log(arr.toString())
// concatenation with empty string at end
console.log(arr + "")
// concatenation with empty string at start
console.log("" + arr);
To add an element to the array use one of this methods.
As #Pranav C Balan mentioned + operator try to concatenate 2 values. That's one of the most weird parts in Javascript. Here is a very helpful page with all of subtleties JS Garden
Example 1:
Var arr = [1,2,3];
arr.push(1)
console.log(arr) // [1,2,3,1]
typeof arr // object
Example 2:
var arr = ["h","e","l","l","o"];
arr.unshift(5);
console.log(arr) // [5,"h","e","l","l","o"];
typeof arr // object

javascript/jQuery Array concatenation?

I have some simple jQuery code:
var response = Array()
$('.task-list').each(function() {
response.concat(response,$('#' + this.id).sortable('toArray'));
}
);
console.log(response);
The problem I'm having is that I think I'm using concat improperly- the result is an empty array. When I use array push, it works correctly.
You have to set response to the newly formed array, as described in the specification. You currently don't use the return value at all.
The specification says that for .concat, the array is not altered, but the new array is returned:
When the concat method is called with zero or more arguments item1, item2, etc., it returns an array containing the array elements of the object followed by the array elements of each argument in order.
Compare with .push, which says the current array is altered, and that something else is returned instead (the new length):
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
So:
response = response.concat($('#' + this.id).sortable('toArray'));
Concat returns the concatenated array, you want this instead
response = response.concat($('#' + this.id).sortable('toArray'));
Simple example
var a = [1,2];
var b = [3,4];
a = a.concat( b ); // result is [1,2,3,4] which is correct
If you do the following
a = a.concat( a , b ); // result is [1, 2, 1, 2, 3, 4] not what you want.

Categories

Resources