I am trying to understand a point of confusion I have with JavaScript objects. Specifically, I am interested in finding what, if anything, causes an object reference to break.
To demonstrate the phenomenon, I have included a copy of some output from Chrome's JavaScript console. Note that I am working with arrays here, but we would expect objects to behave similarly given the subtle distinction between arrays and objects in JS. I have added comments for clarity.
// Set x to some array literal
> x = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
// Set y to x
> y = x
[1, 2, 3, 4, 5]
> x
[1, 2, 3, 4, 5] // as expected
> y
[1, 2, 3, 4, 5] // as expected
As demonstrated above, both x and y output the expected value. Now I shuffle the values of x using a function called shuffle (specified at the bottom of this question).
// Shuffle x
> x = shuffle(x)
[5, 1, 4, 2, 3]
> x
[5, 1, 4, 2, 3] // x changes as expected
> y
[5, 1, 4, 2, 3] // y changes as expected
Again, everything works as expected above. The variables x and y have maintained reference to the same object. However, when we repeat this operation, the results are strange.
// Shuffle x
> x = shuffle(x)
[3, 1, 5, 4, 2]
> x
[3, 1, 5, 4, 2] // x changes as expected
> y
[5, 1, 4, 2, 3] // y didn't change this time
Below is the shuffle function, adapted from here. Its purpose is to shuffle the contents of an array (parameter r1) and to return the first n items of the mixed array.
function shuffle(r1,n) {
var i = r1.length, j, tempi, tempj, r2;
r2 = r1;
while (--i) {
j = Math.floor(Math.random() * (i + 1));
tempi = r2[i];
tempj = r2[j];
r2[i] = tempj;
r2[j] = tempi;
}
return r2.slice(0,n);
}
I have since fixed the problem by rewriting my shuffle function based on this function. However, I would still like to understand what's going on. For a quick look at the code in action, I have made a jsFiddle.
Any ideas? I appreciate your time.
If you remove the .slice(0,n);, it will behave the way you expect. slice makes a new array.
So the first time you call shuffle, within your loop you modify the array x = y = r1 = r2. Then you make a copy of it on that last line and assign that to x. Now x !== y, but they contain the exact same elements. You can test that they are distinct objects after your first call to shuffle:.
The next time you call shuffle you are shuffling the copy of x you made and y is untouched.
.slice() makes a shallow copy of the Array, and so you're overwriting x with a new Array.
// The original was shuffled, but now `x` is a new Array
x = shuffle(x);
That's why y showed the first shuffle (because you hadn't sliced it yet), but none thereafter. The subsequent shuffle was on the overwritten x, and y still references the original.
If you wanted to truncate the original Array, just change its .length.
So instead of this:
return r2.slice(0,n);
Do this:
r2.length = n;
...though you're not passing anything to n currently.
Related
Could someone suggest how to approach this please? I need to display a position in a JavaScript loop using "i" but would like to keep the same position if number of points are the same. Please see the simplified screenshot.
I tried different types of loops but couldn't achieve the desired effect.
You've pretty much answered your question already. Just don't count up i when the points are the same, while looping through your points.
Without any code example I just assumed your points were stored in an array.
const points = [6, 4, 2, 2, 2, 1];
let i = 0;
points.forEach((point, index) => {
if (point !== points[index - 1]) {
i++;
}
console.log(i); // 1, 2, 3, 3, 3, 4
});
This should be your expected result.
Note that his isn't the only solution to the problem, just one of many on how to loop though and count something up. You can use a regular for loop, forEach like I did, while, or whatever you want.
Here's another solution that might be fun, using Array.reduce to create a new Array of Positions
const points = [6, 4, 2, 2, 2, 1];
const positions = points.reduce((acc, point, index) => {
if (point !== points[index - 1]) {
acc.push(acc[index - 1] + 1 || 1);
} else {
acc.push(acc[index - 1]);
}
return acc;
}, []);
console.log(positions); // [1, 2, 3, 3, 3, 4]
I met an unexpected result when I count the number of common elements in two arrays. When I use reduce function, it doesn't work. But filter version returns correct result. I want to know what's wrong with my reduce version.
var ls = [1, 2, 3, 4, 5];
var ms = [4, 5, 6, 1, 2];
console.log(ls.reduce((acc, e) => (ms.includes(e) ? 1 + acc : 0), 0));
// 2, incorrect
console.log(ls.filter(e => ms.includes(e)).length);
// 4, correct
Because in your reduce version, when the element is not found, you reset the acc back to zero instead of returning it as is
var ls = [1, 2, 3, 4, 5];
var ms = [4, 5, 6, 1, 2];
console.log(ls.reduce((acc, e) => (ms.includes(e) ? 1 + acc : acc), 0));
// -----------------------------------------------------------^ here
// 4, now corrected
When your callback function that's the first parameter of the reduce function hits the third item in the ls array, "3", it finds that it's not a member of the ms array. This causes the ternary operator to return the right hand expression 0, which resets your accumulator variable, acc. This will restart the count at 0.
Instead, you should return the current value of the accumulator variable instead of 0 like this:
console.log(ls.reduce(acc,e) => (ms.includes(e) ? 1 + acc: acc), 0));
That will give you the correct count of 4 matching elements.
Struggling to understand how this code works
function countup(n) {
if (n < 1) {
return [];
} else {
const countArray = countup(n - 1);
countArray.push(n);
return countArray;
}
}
console.log(countup(5));
I can understand why once it gets to the Array.push that it prints out [1], but after that I don't understand how it goes from [1] to [1,2,3,4,5].
Also, wouldn't (n-1) always have to be 1-1 as without it the if (n < 1) won't be true?
It should all become clear if you step through it line by line in a debugger:
I wasn't able to record it as slowly as I would have liked to, because I was running into some max. sizes for creating a GIF from it. It's probably best if you do the same yourself anyway. Watch the call stack and the values of the local variables. When you do this interactively, I'd encourage you to also explore the local variables of the higher-up instances of your function in the call stack (you can click on a call stack entry to switch to its scope).
countup(5) is called, so n = 5. Since 5 is not less than 1, we go to the else branch. With n = 5, we get n - 1 = 4 so countup(4) is called.
Same as #1, but with n = 4, eventually calling countup(3).
Same as #1/#2 several more times until we end up calling countup(0). At this point we have a total of 6 instances of the function in the call stack.
With n = 0, we enter the first branch of the if, returning an empty array [].
The countup(1) instance receives the return value of [] from countup(0) and stores it into countArray.
The countup(1) instance pushes n (1) into countArray, yielding [1]. The array is then returned to the called (countup(2)).
The countup(2) instance receives the return value of [1] from countup(1) and stores it into its own countArray.
The countup(2) instance pushes n (2) into countArray, yielding [1, 2]. The array is then returned to the caller (countup(3)).
Steps #5-8 continue for countup(3), countup(4) and countup(5), until at the end countup(5) pushes 5 into its countArray, ending up with [1, 2, 3, 4, 5], and that array is now returned to the caller (the main function).
The main function got the result [1, 2, 3, 4, 5] from countup(5) which is now passed into console.log.
You can also think about it like this:
countup(0) returns []1.
countup(n) for any nonzero n returns [...countup(n - 1), n]2.
(...where ...array means the spread operator so [a, ...[b, c], d] becomes [a, b, c, d])
So we get the following evolution:
Upwards:
countup(0) = []
\_______________________
\
countup(1) = [...countup(0), 1] = [...[], 1] = [1]
______/
/
countup(2) = [...countup(1), 2] = [...[1], 2] = [1, 2]
____/
/
countup(3) = [...countup(2), 3] = [...[1, 2], 3] = [1, 2, 3]
____/
/
countup(4) = [...countup(3), 4] = [...[1, 2, 3], 4] = [1, 2, 3, 4]
____/
/
countup(5) = [...countup(4), 5] = [...[1, 2, 3, 4], 5] = [1, 2, 3, 4, 5]
Downwards:
countup(5) = [...countup(4), 5]
|············\__ \__
|···············\ \
= [...countup(3), 4, 5]
|············\__ \__ \__
|···············\ \ \
= [...countup(2), 3, 4, 5]
|············\__ \__ \__ \__
|···············\ \ \ \
= [...countup(1), 2, 3, 4, 5]
|············\__ \__ \__ \__ \__
|···············\ \ \ \ \
= [...countup(0), 1, 2, 3, 4, 5]
|··· _______/ | | | | |
|···/ | | | | |
= [...[], 1, 2, 3, 4, 5]
\_x_/ | | | | |
= [ 1, 2, 3, 4, 5]
1: Technically, any n < 1 would make countup(n) return [], not only n = 0.
2: Technically, the same array is used all the time here and just mutated in every step. In a pure functional way of handling this, a copy would have to be created (const countup = n => n < 1 ? [] : [...countup(n - 1), n]). But that doesn't matter for this explanation because the array is of course no longer needed in the previous function after it was returned.
Adding some logging should help you understand the execution sequence. This is triggered initially with countup(5), which then recursively calls countup(n-1) until n-1 is 0. That returns an empty array, and then each previous call of countup appends n to the array and returns it. So you end up with an execution order like:
countup(5)
calls countup(4)
calls countup(3)
calls countup(2)
calls countup(1)
calls countup(0), which returns [] to countup(1)
the call from countup(1) appends 1 to the (empty) array and returns [1] to countup(2)
the call from countup(2) appends 2 to the array and returns [1, 2] to countup(3)
the call from countup(3) appends 3 to the array and returns [1, 2, 3] to countup(3)
the call from countup(4) appends 4 to the array and returns [1, 2, 3, 4] to countup(4)
the call from countup(5) appends 5 to the array and returns [1, 2, 3, 4, 5]
function countup(n) {
console.log('countup('+n+')');
if (n < 1) {
console.log('returning empty array');
return [];
} else {
console.log('calling countup - 1');
const countArray = countup(n - 1);
console.log('pushing '+n);
countArray.push(n);
console.log('returning countArray:');
console.log(countArray);
return countArray;
}
}
console.log(countup(5));
Recursive approaches are interesting but kind of difficult to understand at first glance. In this particular example, the function evaluates for a very specific constraint. If n < 1 the function will return an empty Array.
Let's dive into the code execution:
On the first iteration n = 5 that allow the else block to be executed. Once the second countup call (countup(n - 1)) it evaluates the input again, and since n is still greater than 0 the whole process will repeat itself.
Once the countup function receives an input of 0 (n = 0) it returns an empty array. Such array is then assigned to the countArray variable (in this particular point countArray = []) and the current value of n is pushed into the array (since the n = 0 case already returned, you'll land on the case where n=1). Again this process will repeat itself in every step up until you reach the case where n=5 (technically your first iteration). Once 5 has been pushed into the array, the function will return the array containing every value from 1 to the provided number n sorted from the smallest to the largest number.
It's not 100% clear to me how this piece of code works:
var a = [1, 2, 3];
[x, y, ...a ] = [0, ...a, 4];
// OUTPUT: [0, 1, 2, 3, 4]
I'm deconstructing the array a using the ... operator.
I am expecting that in second line a bunch of assignments will take place.
The x will be assigned to 0, y will be assigned to ...a (which passes the elements in the array a as individual values).
It's not clear to me, though, how the ...a get assigned to 4. In fact, JS throws an exception when doing:
...a = 4;
// Uncaught SyntaxError: Rest parameter may not have a default initializer
Why does this code output the modified array with the end 4, instead of throwing an exception? How does this work exactly?
It is executed like following
var a = [1, 2, 3];
[x, y, ...a ] = [0, ...a, 4];
[x, y, ...a ] = [0, 1, 2, 3, 4];
which means first value in RHS array is assigned to x, second value in RHS array is assigned to y and the remaining values are assigned to a.
Hence, value of x is 0, y is 1 and a is [2, 3, 4]
It's not clear to me, though, how the ...a get assigned to 4.
It's not.
Lets split things up a little:
On the right hand side of the assignment you are using an array literal with a spread element. The value of a is "flattened" into the new array.
Thus, [0, ...a, 4] is is equivalent to [0].concat(a, [4]). The result is a new array.
var a = [1, 2, 3];
console.log('spread element', [0, ...a, 4]);
console.log('concat', [0].concat(a, [4]));
On the left hand side you are using array destructuring with a rest element. [x, y, ...a ] means
assign the first value of the iterable to x
assign the second value of the iterable to y
assign the remaining values of the iterable as an array to a
These two are equivalent:
var a = [1,2,3,4];
var [x, y, ...z] = a;
console.log('destructuring', x, y, z);
var x = a[0];
var y = a[1];
var z = a.slice(2);
console.log('manual + slice', x, y, z);
Of course combining these two is perfectly fine. In an assignment, the left hand side doesn't care what how the right hand side is computed and vice versa.
What's confusing about your example is that you are using a again in the destructuring assignment, but that's the same as overriding the value of a with a new value. However the end result is
[0, ...a, 4] results in [0,1,2,3,4] therefor
x has value 0
y has value 1
a has value [2,3,4]
In fact, JS throws an exception when doing: ...a = 4;
The error message you are getting is strange. It should really be just a syntax error.
... by itself doesn't mean anything. It's not an operator, it's a punctuator (like ; or ,) that has a different meaning depending on the context it is used (and allowed).
See also What is SpreadElement in ECMAScript documentation? Is it the same as Spread operator at MDN?
...a is either equal to .slice(start, end) (left, destructuring) or to .concat(a) (right, spreading):
[0, ...a, 4]
is equal to:
[0].concat(a).concat([4]) // [0,1,2,3,4]
Whereas:
[x, y, ...a] = array
Is equal to:
x = array[0];
y = array[1];
a = array.slice(2);
In the first example, spread ie ... in LHS acts as gatherer whereas on the RHS it acts as spread/rest. IE you are assigning value to variable a when it is on LHS.
var a = [1, 2, 3];
[x, y, ...a ] = [0, ...a, 4];
console.log(a)
Let's go step by step:
Let's start with RHS. Doing [0, ...a, 4] will generate [0, 1, 2, 3, 4]. See for yourself:
var a = [1, 2, 3];
console.log([0, ...a, 4]);
Now, the LHS is the side where assignment is taking place. On RHS, imagine any variable with spread operator as an array ready to be assigned new values.
So, we are trying to assign [0, 1, 2, 3, 4] to a variable x, then to y and the rest to array a (in that order). So, array a will have whatever will be left after first two assignments (ie 2, 3, 4).
var a = [1, 2, 3];
// a will get overwritten
[x, y, ...a ] = [0, 1, 2, 3, 4];
// same as [x, y, ...a ] = [0, ...a, 4];
console.log(a);
Finally, coming to your last question: "It's not clear to me, though, how the ...a get assigned to 4? "
Answer: It is not. But if you do something like [...a] = [4], it will result in an array named a containing [4].
You can read more about spread syntax here (MDN) and here (YDKJS).
I am trying a JavaScript challenge. Not to use standard array reverse method but instead creating a new function to modify an array that given as argument and reverse its elements. Here is the example:
var arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [5, 4, 3, 2, 1]
However, I created this function but it didn't work:
function reverseArrayInPlace(arr) {
var newArr = [];
for (var i = arr.length - 1; i >= 0; i--) {
newArr.push(arr[i]);
}
arr = newArr; //This reverse arr successfully but won't work when called
return arr;
}
var arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [1, 2, 3, 4, 5], why? The arr variable above returned [5, 4, 3, 2, 1] but not here
This is the answer and it worked:
function reverseArrayInPlace(arr) {
for (var i = 0; i < Math.floor(arr.length / 2); i++) {
var old = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = old;
}
return arr;
}
var arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [5, 4, 3, 2, 1]
What is wrong with my method. What I don't get is the console.log did output the right reverse order but it will still show the original arrayValue when output. Can someone explain the difference to me?
The assignment is asking to modify the array in place, not to allocate new array/reassigning reference. This means you have to modify the variable passed as argument without allocating new memory for the variable returned (so you don't have another memory reference). The value you return from your function is not allocated to the original variable as it belongs to another scope and no assignment of returned value is performed (i.e. arrayValue = reverseArrayInPlace(arrayValue))
About in place algorythm, from wikipedia: In computer science, an in-place algorithm is an algorithm which transforms input using no auxiliary data structure. However a small amount of extra storage space is allowed for auxiliary variables. In-place algorithm updates input sequence only through replacement or swapping of elements. -> So you have to modify the original array passed, not to allocate a new one. As arr is inside function it is different from arr outside. The arr argument passed to function (which is a reference, not the whole thing) occupies different memory that arr outside
Your script works. If you try to display it something like this:
<p id="result"></p>
<script>
document.getElementById("result").innerHTML = reverseArrayInPlace(arrayValue);
</script>
it works
Here, see:
https://plnkr.co/edit/GJ57go?p=preview