Please check the following...
var arr = [["test", 1], ["test", 3], ["test", 5]]
var otherArr = arr.slice(0) //should be a new array with a copy of arr
When i evaluate arr === otherArr the result is FALSE.
When i do the following, trying to change first array value:
otherArr[0][1] = otherArr[0][1] + 5;
it also changes the original array (arr)
arr[0][1] === otherArr[0][1] evaluates to TRUE
but arr === otherArr evaluates to FALSE
Please help me understand this to avoid it.
This is best explained visually. Imagine arr as the following diagram. It's actually composed of 3 arrays, one referencing the other two:
arr [ 0 , 1 ]
| |
v v
['test', 1], ['test', 5]
When you did otherArr.slice(0), it creates a "shallow copy" - a new array but with identical contents to the original array. That means arr and otherArr are two separate arrays, but point to the same contents. arr[0] is the same object as otherArr[0], and arr[1] is the same object as otherArr[1]
arr [ 0 , 1 ]
| |
v v
['test', 1], ['test', 5]
^ ^
| |
otherArr [ 0 , 1 ]
Now to your questions:
When i evaluate arr === otherArr the result is FALSE.
As mentioned above, arr and otherArr are two different arrays, causing === to fail. For non-primitives, === checks the identity (i.e. is it the same object?). Also note that === and == are NOT structural checks (i.e. do they look the same?).
When i do the following, trying to change first array value:
otherArr[0][1] = otherArr[0][1] + 5;
it also changes the original array (arr)
arr[0][1] === otherArr[0][1] evaluates to TRUE
Back to our diagram, what you're effectively doing is changing the contents of the object both arrays are referencing (in this case, the one that's now ['test', 6]).
arr [ 0 , 1 ]
| |
v v
['test', 6], ['test', 5]
^ ^
| |
otherArr [ 0 , 1 ]
This is because the arrays are different objects from the slice()
arr === otherArr // false (different objects)
But the values stored in the arrays are the the same objects
arr[0][1] === otherArr[0][1]
If you do not want this behavior, you will need to do a deep copy of the array instead of a slice.
Here is some information on deep copy as it relates to arrays: https://www.cs.utexas.edu/~scottm/cs307/handouts/deepCopying.htm
The problem is
The slice() method returns a shallow copy […]
You are replicating just the first level, so you have to go deeper to made a copy. You could do:
var arr = [["test", 1], ["test", 3], ["test", 5]]
var otherArr = [...arr[0], ...arr[1], ...arr[2]]
otherArr[0][1] = otherArr[0][1] + 5;
console.log(arr)
The JSON.parse(JSON.stringify(arr)) method is the best solution in general.
When i evaluate arr === otherArr the result is FALSE.
because a slice is not the same array.
it essentially is a copy of the arrays content.
When i do the following, trying to change first array value:
otherArr[0][1] = otherArr[0][1] + 5;
it also changes the original array (arr)
arr[0][1] === otherArr[0][1] evaluates to TRUE
but arr === otherArr evaluates to FALSE
yes because an array is a object and not a primitive value.
you are copying all the contents of arr into otherArr
but you are not creating copies of it's referenced objects.
if you had simple values in there like numbers, strings, boolean and probably even a regexp you could be sure that it would be copied.
if you access a sub array you need to create a slice of that sub array first too if you want an actual copy.
because arr is just pointing to the arrays. it does not contain them. they are references.
please also take a look at the comment from Calvin Nunes below your question:
you can create a deep copy by converting your array into a json string first and then back into an array:
otherArr = JSON.parse(JSON.stringify(arr))
keep in mind this works with simple stuff but if you want to copy objects that contain custom functions etc. you can forget about this.
they would not be added to the json representation and are lost.
json is just a format meant for transportation.
if you stumble across such a requirement in future you might want to consider rewriting your code instead.
this always leads to slow code.
you may want to recursively copy your array instead.
like.. walking through the array, if the member is of type array, walk through it too.
well.. there are various deep copy solutions out there. you just need to google for it.
Related
i was traying to do the odin project exercises of javascript,
when I execute this line in the console
2 in [2,3,4,5]
it gives
true
but when I execute 3 in [3,4,5] in the console it gives back false I think it should also be true!!!
any explanation please
thank you in advance
The in operator checks if a property key exists on an object. You can think of this array as having 4 keys.
const arr = [2, 3, 4, 5];
// arr has the keys `0`, `1`, `2`, and `3`.
console.log(arr[0], arr[1], arr[2], arr[3]);
So when you have the expression 2 in arr, you are checking if the array has a key 2, not if the value 2 exists in the array.
If you do want to check if an array contains an item, the includes() method would achieve what you want.
const arr = [2, 3, 4, 5];
arr.includes(2); // true
arr.includes(0); // false
It behaves like this because the in operator returns true if the specified property (not value) is in the specified object.
So in the first case the array has a length of 4 with indices 0,1,2,3 so the element with the index 2 exists.
In the second case there are 3 elements with indices 0,1,2, so there is no element with an index 3 that is why it returns false.
I'm in a situation in which for me it is vital to add two arrays together and create a new array which does not have the memebers of tow arrays which are the same being added to the final array as multiple members but rather just one,I just want to have one memeber as the representive of those identical members from two arrays in the final array, not having them as multiple memebers in the final array,I tried concat() and it did exactly what I don't want.
How can I do this, guys?
like:
let arrayone = [2,4,6];
let arraytwo = [2,4,7];
I want the final array to be
finalarray = [2,4,6,7]
but concat() gives me
final array [2,4,6,2,4,7]
This was just for more clarification, the members of my arrays are objects.
In set theory, this is called the "union". There are a number of ways to do this in JavaScript. If the arrays only contain primitive values, you can simply build a Set out of them, and then spread back into an array:
const firstArray = [1, 2, 3]
const secondArray = [3, 4, 5]
const union = [...new Set([...firstArray, ...secondArray])] // [1, 2, 3, 4, 5]
The Set constructor ensures that no duplicate values are added to the set. If the values of the arrays are NOT primitives (i.e. objects or something else), you'll need to use some other means of iteration to achieve a union. Here is a good example of deriving the union of two arrays of objects.
From mdn: Spread Syntax
Note: Typically the spread operators in ES2015 goes one level deep while copying an array. Therefore, they are unsuitable for copying multidimensional arrays. It's the same case with Object.assign() and Object spread syntax. Look at the example below for a better understanding.
var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift(); // 1
// Now array b is: [[2], [3]]
What is the point of the above statement? The above code sample works just the same as if you'd copied the array in a to b using the .slice() method. I tried adding another dimension to the array here: https://repl.it/HKOq/2 and things still worked as expected.
So why is the spread syntax unsuitable for copying multidimensional arrays?
I'd appreciate any help.
EDIT:
Reading the answers by estus and vol7ron helped me figure things out. Basically, as estus points out technically there are just arrays inside arrays rather than multidimensional arrays.
And as vol7ron explains only the first level of the array is copied so the objects in memory remain the same for any further nested elements.
I was also wrong to suspect that using the spread syntax was supposed to behave any differently than the slice operator
Man, programmers are really poor at displaying examples that actually show the difference.
var a = [[['a', 'b'], ['c', 'd']], 'e'];
var b = [...a];
b[0][0][0] = 'z';
b[1] = 'x';
console.log('a', a);
console.log('b', b);
This outputs:
a [[["z", "b"], ["c", "d"]], "e"]
b [[["z", "b"], ["c", "d"]], "x"]
Notice something fishy? Both arrays [0][0][0] value was changed. Meaning that the object sitting at [0][0][0] in both arrays are referenced to the same object, and is not a copy. However the [1] values are different meaning that it is indeed a copy.
Shallow copy means the first level is copied, deeper levels are referenced.
Arrays are objects, and [...a] creates a shallow copy of a array object.
For the language itself there are no multidimentional arrays - there are another arrays inside an array. It doesn't matter if contains arrays, plain objects, functions or primitives. For primitives, their values will be copied. Otherwise, the references to objects will be copied. This is what
It's the same case with Object.assign() and Object spread operators
part refers to.
And regarding
The above code sample works just the same as if you'd copied the array in a to b using the .slice() method
...it truly does. This is a neater way to write a.slice() or [].concat(a). With a considerable exception. ES6 rest operator (as well as Array.from(a)) works equally for all iterables, not just for arrays.
For a deep copy of an object ES6 offers nothing new, an object (which an array is) should be recursively copied by hand. To address all the concerns it still makes sense to use proven third-party helper functions, such as Lodash cloneDeep.
New arrays are not created for internal array elements (for multi-dimensional array):
// One-dimensional array
var a = [1,2,3];
var b = [...a];
a[0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0] == 1
// got: b[0] == 1
// Multi-dimensional array
var a = [[1], [2], [3]];
var b = [...a];
a[0][0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0][0] == 1
// got: b[0][0] == 'a'
It works like slice(), so you would have to traverse the array and create new arrays for each dimension. Here's one quick example:
// Multi-dimensional array
var a = [[1], [2], [3]];
var b = (function fn(ar){
return ar.map(el=>Array.isArray(el)&&fn(el)||el)
})(a);
a[0][0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0][0] == 1
// got: b[0][0] == 1
So what the example is trying to convey is that var b = [...a]; will not unroll the inner arrays of a (e.g b = [1,2,3]), but instead, b will be [[1],[2],[3]]. So b.shift() removes and returns the first element of b which is [1], then the second shift() just removes 1 from that returned array. In one word ... only reaches one level down into your spreaded array, e.g. var b =[...a] is equivelent to var b = [a[0], a[1], a[2]], not var b = [ a[0][0], a[1][0], a[2][0] ] in the example
I try to start an array index at 1 with
var entry = {};
entry['idSong'] = idSongVoted;
entry['voted'] = votedsong;
$scope.votedList.splice(idSongVoted, idSongVoted, entry);
but i have always starter at zero :
i need the index is same as the idSong
idSongVoted is a integer with values 1 to 12, without zero
The javascript arrays usually start with the index 0.You can start with the index as 1 with sparse array(object).
So here you need is Javascript objects
This problem is caused by the incorrect usage of splice. While JavaScript arrays are 0-based, neither this nor being a "sparse array" nor "not a normal object" are immediately relevant.
The splice function is simply not the correct tool for this task1.
var a = []
a.splice(1, 1, "a")
// a => ["a"]
a.splice(3, 3, "c")
// a => ["a", "c"]
a.splice(2, 2, "b")
// a => ["a", "c", "b"]
A trivial fix is to assign to each array index directly - if a "normal object" was used it would be considered assigning to each property directly.
var a = []
a[1] = "a"
a[3] = "c"
a[2] = "b"
// a => [undefined, "a", "b", "c"]
In context of the code the fix would simply be:
$scope.votedList[idSongVoted] = entry;
One nice thing about using a "sparse array" as shown - as no value was assigned to index 0 - is that Arrays.forEach and angular.forEach will correctly enumerate items in the numerical order of "id" and skip the indexes without values assigned. (If a value like null or undefined were assigned to the 0th index then the forEach loops would need guards added!)
However, if the "id" values are not relatively densely packed non-negative integers around 0 then a "normal object", which is supported by angular.forEach, would be a much better choice for an "id"-to-entry Map. This is for both logical/semantic and performance reasons.
1 Per the MDN Array.splice documentation:
Index at which to start changing the array. If greater than the length of the array, actual starting index will be set to the length of the array. If negative, will begin that many elements from the end.
This means a.splice(1, 1, "a"), when a is an empty array, is equivalent to a.splice(0, 1, "a"). This same issue also affects a.splice(3, 3, "c") when the array only has two elements - which leads to the overall incorrect ordering of ["a", "c", "b"] when the "id" used is not strictly ordered.
Supplying the "id" to howMany is also problematic, although such is not shown in the example. Just don't use splice here.
I have two arrays, one comes as a reference (parameter) from a function, and the other is created as part of the function - exactly the same scenario as described here:
Add two arrays without using the concat method
I was using the push.apply() method as per the suggestion above, but can someone please explain to me, why I can't use concat() to merge two arrays if the array is sent into the function as a reference?
Refer to Array.concat on MDN:
Any operation on the new array will have no effect on the original arrays, and vice versa.
This makes it behave differently from Array.push.apply which will mutate the original Array object - the return value of Array.concat must be used. Otherwise, it works as explained in the MDN link above.
If you use concat the original array will be unmodified. If you have a reference to it you wont see the new elements.
var arr1 = [ "a", "b" ];
var arr2 = [ "c", "d" ];
arr1.push.apply(arr1, arr2);
Basically does this:
[ "a", "b" ].push("c", "d");
apply turns an array into a list of arguments. The first argument to apply is the context by the way, arr1 in this case since you want the push to apply to arr1.
You can use concat:
var arr1 = [ "a", "b" ];
var arr2 = [ "c", "d" ];
var arr3 = arr1.concat(arr2);
This leaves the original arr1 as it was. You've created a new array that has both arr1 and arr2 elements in it. If you have a reference to the original arr1 it will be unmodified. That might be a reason to not want to use concat.
Let's say we have 2 arrays "a" and "b". Array.concat method will return new instance of Array "c" which represents concatenation between a and b without any mutation of a or b. Array.push return last index of pushed element and mutate this instance.
Since ES6 (or 15, not sure) it's possible to unpack parameters and you can use push to concatenate (without harmful code) as well
a = [1,2,3,4]; // a=[1,2,3,4];
b = [5,6,7,8]; // b=[5,6,7,8];
a.push(...b) // a=[1,2,3,4,5,6,7,8]; b=[5,6,7,8]