Why array value is changed only after first function? - javascript

I have a function that reverses an array:
function reverseArray(array) {
for(let i = 0; i <= Math.floor(array.length / 2); i++) {
let old = array[array.length - 1 - i];
array[array.length - 1 - i] = array[i];
array[i] = old;
}
return array;
}
and a function that creates an local scope array and push values in reversed order:
function reverseArr(arr) {
let output = [];
for(let i of arr) {
output.unshift(i);
}
arr = output;
return arr;
}
Suppose there is an element:
let arrayValue = [1, 2, 3, 4, 5];
If i invoke the first function with arrayValue as argument, arrayValue is changed:
reverseArray(arrayValue); // [5, 4, 3, 2, 1]
console.log(arrayValue); // [5, 4, 3, 2, 1]
However if i invoke the second function with arrayValue:
reverseArr(arrayValue); //[5, 4, 3, 2, 1]
console.log(arrayValue); //[1, 2, 3, 4, 5]
arrayValue is not changed even if i assigned the reversed value to the argument before return:
arr = output;
can someone explain me why?
thanks in advance.

Basically you take a new array with
let output = [];
and later you assign the new array to the parameter arr
arr = output;
Now you have two object references, one arr of the outer scope and a new one of output.
To overcome this, you need to keep the object reference of array. For getting a new content in an existing array, you could empty the array and push the values of the new array.
arr.length = 0;
arr.push(...output);

When you do arr = output in reverseArr you are referring to a new array. In other words arrayValue in the outer context and arr refer to two different objects.
You cannot change the value of the variable the function is called with. If you have a reference to the object you can mutate it, but you cannot make the outside variable refer to another object.

Basically, your first function reverses the list in-place (i.e. operates on the list itself directly, without building a new list), while the second function reverses the list out-of-place, by building a new list which contents are the reverse of the original list.
When working in-place, the changes you do to array inside the function are directly applied to arrayValue.
The reason why arr = output does not work the way you intend is pretty much what the other answers refer to. Namely, arr and arrayValue are two different references ("pointers"). Initially, both arrayValue and arr "point to" the same array when the function is called. However, arr = output makes arr point to the newly built list output instead.

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 to change position of items within an array?

I am trying to create a function that changes positions of the items inside an array.
For example, I have this array:
[1, 2, 3] - if I call the function I get this => [2, 3, 1];
If I call the function again I should have this => [3, 1, 2];
The next time it should move again and so on.
But instead, if I call the function a second time I get this again => [2, 3, 1];
How could I make this work properly?
I know why this is happening, every time I call the function it receives the same values in the same order, I get that, that's why I built another array (move) to receive the values in the current order, it was an attempt to use this new array the second time I call the function, I tried it using an if/else statemente, but it didn't work; I also built another function passing 'move' as a parameter, and it worked, but just doesn't make any sense I have to have a different function for every move.
p.s. I am receiving the values from a HTML input.
<input id="numero1" value="">
<button onclick="moveLeft()">Move</button>
var n1 = document.getElementById("numero1");
function moveLeft() {
var str = n1.value;
var arr = str.split('');
var move = [];
console.log(arr);
arr.splice(0, 3, arr[1], arr[2], arr[0]);
console.log(arr);
for (var i = 0; i < arr.length; i++) {
move.push(arr[i]);
}
console.log(move);
teste(move);
}
function teste(move) {
console.log(move);
move.splice(0, 3, move[1], move[2], move[0]);
console.log(move);
}
If I understood properly, I think you can simplify in one line function:
const moveLeft = (arr) => arr.push(a.shift());
Notice that this function would mutate the content of the array but I guess that what you want:
const array = [1, 2, 3];
moveLeft(array);
console.log(array)// [2, 3, 1]
moveLeft(array);
console.log(array)// [3, 1, 2]
moveLeft(array);
console.log(array)// [1, 2, 3]
Basically with push we add a new element at the end of the array, and the new element we add, is the return value of the method shift, that removes the first element from an array and return it.
Edit
If you don't want to mutate the original array, the fastest way is create a shallow copy and pass that to the function:
const newArray = array.slice();
moveLeft(newArray);
console.log(newArray, array) // [2, 1, 3] [1, 2, 3]
However, a possible implementation that always return a new array, could be:
const moveLeft = (arr) => [...arr.slice(1), arr[0]]
Just for future reference. But keep in mind that here you're create a shallow copy, (so one new array) and returns a new array from the spread operator, so it's less ideal. It could be useful only if you're writing an API that never allow mutability.
Edit 2
As per comment, the input's value should reflect the new order, so, given the moveLeft function written before (the first one, that mutates the content):
<input id="numero1" value="">
<button onclick="inputMoveLeft('numero1')">Move</button>
And the JS:
function inputMoveLeft(id) {
const input = document.getElementById(id);
input.value = moveLeft(input.value.split("")).join("");
}
It should give you the result you were looking for.
pop and shift sound like the thing you need:
function rotateLeft(array){
let first = array.shift()
array.push(first); // add to end
return array;
}
function rotateRight(array){
let last = array.pop()
array.unshift(last); // add to front
return array;
}

Why can't you swap the arrays of a 2D array by reference?

I have a Matrix contained in a 2D-array:
var testM = [
[0.0,2.0,3.0],
[1.0,1.0,1.0],
[7.0,5.0,6.0]
];
// swap row a with b
function swapRows(a,b){
let temp = a.slice(); // Copy by value, not reference
a = b.slice();
b = temp;
}
// The individual arrays are passed in:
swapRows(testM[0], testM[1]);
However, the original 2D array remains unchanged after running the above code. I'm not too familiar with passing by reference in javascript, so any help is appreciated. (Note: it works fine when pasted inline, just wondering why it doesn't function even when copied by value)
EDIT:
I completely understand how to swap the elements of a regular, one-dimensional array; I was inquiring more about about the mechanics of passing by reference in javascript. (I don't even know if I'm using the right terminology)
Your code doesn't work because assigning to a and b inside your function doesn't change the references in the original table.
You pass the function a reference to two of the internal arrays from the table. The function creates local variables a and b each pointing to one of those references. When you swap a and b all you have done is changed which array reference the local variables point to. You haven't changed anything in the original table. To do that you need a reference to the table itself so you can swap what its array references point to. Something like:
var testM = [
[0, 2, 3],
[1, 1, 1],
[7, 5, 6]
];
// swap row a with b
function swapRows(table, a, b){
[table[a], table[b]] = [table[b], table[a]]
}
// Table and indexes passed in:
swapRows(testM, 0, 1);
console.log(testM)
If you were dead set on passing in the individual arrays, you could replace all the values of one array with the other. This is really inefficient though:
var testM = [
[0, 2, 3],
[1, 1, 1],
[7, 5, 6]
];
// swap row a with b
function swapRows(a,b){
let temp = a.slice(); // Copy by value, not reference
a.splice(0, a.length, ...b);
b.splice(0, b.length, ...temp);
}
// The individual arrays are passed in:
swapRows(testM[0], testM[1]);
console.log(testM)
Edit
You can't alterate a primitive (string, number, bigint, boolean, null, undefined, and symbol) because primitives are immutable.(https://developer.mozilla.org/en-US/docs/Glossary/Primitive)
If you pass the array as an argument, you can alterate it inside your function.
swapRows(testM, 0, 1);
function swapRows(arr,a,b){ // <-- arr = testM. The value is an ARRAY, therefore it can be alterate (it's not a primitive)
let temp = arr[a].slice();
arr[a] = arr[b].slice();
arr[b] = temp;
}
Old
Basically, you can't pass a variable as a reference in javascript. (This sentence isn't correct. See "Edit")
If you want to manipulate an object, you can adopt one of these methods:
Assign a new value using a function that returns the computed value
Modify the object (some property of the object) by using a method
Function
You have to pass the object (array) as an argument of the function
function swapRows(arr,a,b){
let temp = arr[a].slice(); // Copy by value, not reference
arr[a] = arr[b].slice();
arr[b] = temp;
return arr;
}
testM = swapRows(testM, 0, 1);
Method
You can extend the object or the prototype (some caveat here: JavaScript: What dangers are in extending Array.prototype?)
Array.prototype.swapRows = function(a,b){
let temp = this[a].slice(); // Copy by value, not reference
this[a] = this[b].slice();
this[b] = temp;
return this;
}
testM.swapRows(0, 1);

JavaScript array passed as ref but can't assign ref to a new array?

If I pass an array into a function and make changes to the array within the function, the array that exists outside the function reflects those effects. E.g.:
var myArr = [1, 2, 3];
function popAll(arr) {
while(arr.length) {
arr.pop();
}
}
popAll(myArr);
console.log(myArr.length); // prints "0"
However, if I try to reassign the array reference to point to another array in the function, it does not stick:
var myArr = [1, 2, 3];
function reassign(arr) {
while(arr.length) {
arr.pop();
}
var newArr = [1,2,3,4,5];
arr = newArr;
}
reassign(myArr);
console.log(myArr.length); // still prints "0"!!
What am I missing? I want reassign(...) to assign the reference to the new array.
Edit:
I do not want to return the new array. I want to reassign the incoming reference. Also, I want to learn why JS has this behavior that appears to be inconsistent (or I don't know enough about to understand how it is consistent).
All function calls in JavaScript are pass-by-value.
This may seem counter-intuitive, but it makes more sense if you consider that the arr parameter is itself a reference, so it's a reference, passed by value. Assigning newArr to arr will not modify the old reference, it will simply change what arr references within the scope of the function.
As Zeb Rawnsley pointed out in comments, you must return the new reference from the function and assign the outer variable to the return value:
var myArr = [1, 2, 3];
function reassign(arr) {
while(arr.length) {
arr.pop();
}
var newArr = [1,2,3,4,5];
return newArr;
}
myArr = reassign(myArr);
console.log(myArr.length);
Just pointing out that you don't need to pass in the old reference or empty it, it will automatically be garbage-collected once there are zero references left to the memory that myArr initially allocated:
var myArr = [1, 2, 3];
function reassign() {
return [1,2,3,4,5];
}
myArr = reassign();
console.log(myArr.length);
You can't assign the value of an array to another array, but you can add the elements from one array to another.
This should do the trick for you:
var myArr = [1, 2, 3];
function reassign(arr) {
while(arr.length) {
arr.pop();
}
var newArr = [1,2,3,4,5];
for(var num in newArr){
arr.push(num);
}
}
reassign(myArr);
console.log(myArr.length); //prints 5

Return an array with all the elements of the passed in array but the last

Instructions:
Write a function called getAllElementsButLast.
Given an array, getAllElementsButLast returns an array with all the elements but the last.
Below is my code that will not pass the requirements for the question. I am not sure why this is not correct even though I am getting back all the elements besides the last.
var arr = [1, 2, 3, 4]
function getAllElementsButLast(array) {
return arr.splice(0, arr.length - 1)
}
getAllElementsButLast(arr) // [1, 2, 3]
I think the reason why it's not accepted is because with splice() you change the input array. And that's not what you want. Instead use slice(). This method doesn't change the input array.
var arr = [1, 2, 3, 4]
function getAllElementsButLast(array) {
var newArr = array.slice(0, array.length - 1);
return newArr;
}
var r = getAllElementsButLast(arr);
console.log(r);
console.log(arr);

Categories

Resources