Newbie having some array problems [duplicate] - javascript

This question already has answers here:
Why does changing an Array in JavaScript affect copies of the array?
(12 answers)
Closed 8 months ago.
I am doing my first code project and I am having some problems with arrays changing their values apparently on their own
console.log(permanentDeck) should return 7 cards, but they seem to have been taken off
I've checked more than once for any line that changes permanentDeck value, but didnt find none
Can someone help me figure out in which line this happens??
var playerClass = "Warrior"
const examcard1 = {id:1, cost: 1, name:"Slash", text:"Deal 100% damage"}
const examcard2 = {id:2, cost: 1, name:"Block", text:"Gain 1 block"}
const examcard3 = {id:3, cost: 2, name:"Heavy Strike", text:"Deal 200% damage\nApply 1 vunerable"}
const starterWarriorDeck = [examcard1, examcard1, examcard1, examcard2, examcard2, examcard2, examcard3]
var deck
var permanentDeck = []
switch(playerClass){
case "Warrior":
permanentDeck = starterWarriorDeck
}
var drawPile = []
var hand = []
function shuffle(array){
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
function restockDrawPile(){
drawPile = shuffle(deck);
}
function startBattle(){
deck = permanentDeck;
restockDrawPile();
}
function startPlayerTurn(){
drawCards(5)
}
function drawCards(cardsToDraw){
for(i = 0; i < cardsToDraw; i++){
let nextCard = drawPile.pop();
hand.push(nextCard);
}
}
var handView = new PIXI.Container()
startBattle();
startPlayerTurn();
console.log(permanentDeck);
console.log(deck);
console.log(drawPile);
console.log(hand);
´´´

if you add console.log(starterWarriorDeck);, you'll find that it has been modified as well. This is because arrays are passed by reference, so in the following lines, you're not making a copy of the array, just another reference to the same array:
permanentdeck = starterWarriorDeck;
deck = permanentdeck;
console.log(Object.is(deck, starterWarriorDeck)) // true
You need to explicitly copy the contents into the new array. There are multiple methods of doing a shallow copy in JS, but the clearest ones in this case would be to use the spread operator ... , or the .from method.
Examples:
permanentDeck = [...starterWarriorDeck]
deck = Array.from(permanentDeck)
Relevant info:
https://www.freecodecamp.org/news/how-to-clone-an-array-in-javascript-1d3183468f6a/

When you assign an array (e.g. permanentDeck) to a variable (e.g. deck) with deck = permamentDeck you just assign a reference the same underlying datastructure to deck. This means that deck and permanentDeck will both point to the same entity and if you modify any of the two you modify the same thing that both variables are referencing.
You'd need to make sure to make a true copy of the array to be able to modify deck without modifying permanentDeck
One of the simple methods to achieve such a "shallow copy" is to use the slice function of a javascript array.
// make a shallow copy of the array
deck = permanentDeck.slice();
Have a look at various methods to copy arrays.
https://www.freecodecamp.org/news/how-to-clone-an-array-in-javascript-1d3183468f6a/
Also make sure you understand and you're aware of the difference between a shallow copy and deep copy of arrays consisting of object references.

Related

Keep taking new element from an array

I have an array that whenever I fire a function called X I want to get random element from that array. The function will be called many times and every time I call that function I want to get element I didn't get before. I also need a reference to all elements so I can't be just removing from array Here are my 2 solutions I came up with:
Make a function that gets a random index number excluding some numbers. Problem with function like this is that it basically works that whenever the number is equal to the excluded it just generates again until it gets the right one. So when array has like a 1000 elements but only like 10 are left it will be generating forever.
Make a second array both would be the same at beginning. however I only remove from the second, and I can get random index from the second. Problem is whenever I remove that element using splice() from second array he is actually removed from both arrays. Is there a way to remove just from 1 array?
Am I just making some stupid mistakes and there is some simple solution that I am missing?
This is a perfect use case for a generator function. It will return undefined when you've exhausted the shuffled input array.
const input = [1,2,3]
function* shuffle(arr) {
arr = [...arr] // make a shallow copy, to avoid mutating the original
while(arr.length) yield arr.splice(Math.random()*arr.length|0, 1)[0]
}
let generator = shuffle(input);
console.log(generator.next().value)
console.log(generator.next().value)
console.log(generator.next().value)
console.log(generator.next().value)
You could generate a second array of indexes, shuffle it, and pop an index from it.
Once the index array is empty, refill it.
Something like
array = ['my', 'items', 'to', 'select']
// Fisher-Yates shuffle
function shuffle(ar) {
for(let i = ar.length - 1; i >= 1; i--) {
let temp = Math.floor(Math.random() * (i + 1)); // select an item to swap with
[ar[temp], ar[i]] = [ar[i], ar[temp]]; // do the swap
}
}
let indices = [];
function nextItem() {
if(indices.length == 0) {
// generate the array of indexes
indices = array.map( (item, index) => index );
shuffle(indices);
}
return array[indices.pop()];
}
// Call it a few times
for(let i = 0; i < 10; i++) {
console.log( nextItem() );
}
If you were concerned at all about performance, instead of popping from indices and regenerating a new array, you could instead have a "current index" that walks through the array, and when it gets to the end reshuffle.
For solution 2, you need your second array to be copy of the original, not a reference to the original. Try this:
const originalArray = ['one', 'two', 'three']
let copyArray = [...originalArray]

How do I remove all the elements of an array using splice [duplicate]

This question's answers are a community effort. Edit existing answers to improve this post. It is not currently accepting new answers or interactions.
Is there a way to empty an array and if so possibly with .remove()?
For instance,
A = [1,2,3,4];
How can I empty that?
Ways to clear an existing array A:
Method 1
(this was my original answer to the question)
A = [];
This code will set the variable A to a new empty array. This is perfect if you don't have references to the original array A anywhere else because this actually creates a brand new (empty) array. You should be careful with this method because if you have referenced this array from another variable or property, the original array will remain unchanged. Only use this if you only reference the array by its original variable A.
This is also the fastest solution.
This code sample shows the issue you can encounter when using this method:
var arr1 = ['a','b','c','d','e','f'];
var arr2 = arr1; // Reference arr1 by another variable
arr1 = [];
console.log(arr2); // Output ['a','b','c','d','e','f']
Method 2 (as suggested by Matthew Crumley)
A.length = 0
This will clear the existing array by setting its length to 0. It also works when using "strict mode" in ECMAScript 5 because the length property of an array is a read/write property.
Method 3 (as suggested by Anthony)
A.splice(0,A.length)
Using .splice() will work perfectly, but since the .splice() function will return an array with all the removed items, it will actually return a copy of the original array. Benchmarks suggest that this has no effect on performance whatsoever.
Method 4 (as suggested by tanguy_k)
while(A.length > 0) {
A.pop();
}
This solution is not very succinct, and it is also the slowest solution, contrary to earlier benchmarks referenced in the original answer.
Performance
Of all the methods of clearing an existing array, methods 2 and 3 are very similar in performance and are a lot faster than method 4. See this benchmark.
As pointed out by Diadistis in their answer below, the original benchmarks that were used to determine the performance of the four methods described above were flawed. The original benchmark reused the cleared array so the second iteration was clearing an array that was already empty.
The following benchmark fixes this flaw: http://jsben.ch/#/hyj65. It clearly shows that methods #2 (length property) and #3 (splice) are the fastest (not counting method #1 which doesn't change the original array).
This has been a hot topic and the cause of a lot of controversy. There are actually many correct answers and because this answer has been marked as the accepted answer for a very long time, I will include all of the methods here.
If you need to keep the original array because you have other references to it that should be updated too, you can clear it without creating a new array by setting its length to zero:
A.length = 0;
Here the fastest working implementation while keeping the same array ("mutable"):
function clearArray(array) {
while (array.length > 0) {
array.pop();
}
}
FYI it cannot be simplified to while (array.pop()): the tests will fail.
FYI Map and Set define clear(), it would have seem logical to have clear() for Array too.
TypeScript version:
function clearArray<T>(array: T[]) {
while (array.length > 0) {
array.pop();
}
}
The corresponding tests:
describe('clearArray()', () => {
test('clear regular array', () => {
const array = [1, 2, 3, 4, 5];
clearArray(array);
expect(array.length).toEqual(0);
expect(array[0]).toEqual(undefined);
expect(array[4]).toEqual(undefined);
});
test('clear array that contains undefined and null', () => {
const array = [1, undefined, 3, null, 5];
clearArray(array);
expect(array.length).toEqual(0);
expect(array[0]).toEqual(undefined);
expect(array[4]).toEqual(undefined);
});
});
Here the updated jsPerf: http://jsperf.com/array-destroy/32 http://jsperf.com/array-destroy/152
jsPerf offline. Similar benchmark: https://jsben.ch/hyj65
A more cross-browser friendly and more optimal solution will be to use the splice method to empty the content of the array A as below:
A.splice(0, A.length);
The answers that have no less that 2739 upvotes by now are misleading and incorrect.
The question is: "How do you empty your existing array?" E.g. for A = [1,2,3,4].
Saying "A = [] is the answer" is ignorant and absolutely incorrect. [] == [] is false.
This is because these two arrays are two separate, individual objects, with their own two identities, taking up their own space in the digital world, each on its own.
Let's say your mother asks you to empty the trash can.
You don't bring in a new one as if you've done what you've been asked for.
Instead, you empty the trash can.
You don't replace the filled one with a new empty can, and you don't take the label "A" from the filled can and stick it to the new one as in A = [1,2,3,4]; A = [];
Emptying an array object is the easiest thing ever:
A.length = 0;
This way, the can under "A" is not only empty, but also as clean as new!
Furthermore, you are not required to remove the trash by hand until the can is empty! You were asked to empty the existing one, completely, in one turn, not to pick up the trash until the can gets empty, as in:
while(A.length > 0) {
A.pop();
}
Nor, to put your left hand at the bottom of the trash, holding it with your right at the top to be able to pull its content out as in:
A.splice(0, A.length);
No, you were asked to empty it:
A.length = 0;
This is the only code that correctly empties the contents of a given JavaScript array.
Performance test:
http://jsperf.com/array-clear-methods/3
a = []; // 37% slower
a.length = 0; // 89% slower
a.splice(0, a.length) // 97% slower
while (a.length > 0) {
a.pop();
} // Fastest
You can add this to your JavaScript file to allow your arrays to be "cleared":
Array.prototype.clear = function() {
this.splice(0, this.length);
};
Then you can use it like this:
var list = [1, 2, 3];
list.clear();
Or if you want to be sure you don't destroy something:
if (!Array.prototype.clear) {
Array.prototype.clear = function() {
this.splice(0, this.length);
};
}
Lots of people think you shouldn't modify native objects (like Array), and I'm inclined to agree. Please use caution in deciding how to handle this.
You can easily create a function to do that for you, change the length or even add it to native Array as remove() function for reuse.
Imagine you have this array:
var arr = [1, 2, 3, 4, 5]; //the array
OK, just simply run this:
arr.length = 0; //change the length
and the result is:
[] //result
easy way to empty an array...
Also using loop which is not necessary but just another way to do that:
/* could be arr.pop() or arr.splice(0)
don't need to return as main array get changed */
function remove(arr) {
while(arr.length) {
arr.shift();
}
}
There are also tricky way which you can think about, for example something like this:
arr.splice(0, arr.length); //[]
So if arr has 5 items, it will splice 5 items from 0, which means nothing will remain in the array.
Also other ways like simply reassign the array for example:
arr = []; //[]
If you look at the Array functions, there are many other ways to do this, but the most recommended one could be changing the length.
As I said in the first place, you can also prototype remove() as it's the answer to your question. you can simply choose one of the methods above and prototype it to Array object in JavaScript, something like:
Array.prototype.remove = Array.prototype.remove || function() {
this.splice(0, this.length);
};
and you can simply call it like this to empty any array in your javascript application:
arr.remove(); //[]
If you are using
a = [];
Then you are assigning new array reference to a, if reference in a is already assigned to any other variable, then it will not empty that array too and hence garbage collector will not collect that memory.
For ex.
var a=[1,2,3];
var b=a;
a=[];
console.log(b);// It will print [1,2,3];
or
a.length = 0;
When we specify a.length, we are just resetting boundaries of the array and memory for rest array elements will be connected by garbage collector.
Instead of these two solutions are better.
a.splice(0,a.length)
and
while(a.length > 0) {
a.pop();
}
As per previous answer by kenshou.html, second method is faster.
There is a lot of confusion and misinformation regarding the while;pop/shift performance both in answers and comments. The while/pop solution has (as expected) the worst performance. What's actually happening is that setup runs only once for each sample that runs the snippet in a loop. eg:
var arr = [];
for (var i = 0; i < 100; i++) {
arr.push(Math.random());
}
for (var j = 0; j < 1000; j++) {
while (arr.length > 0) {
arr.pop(); // this executes 100 times, not 100000
}
}
I have created a new test that works correctly :
http://jsperf.com/empty-javascript-array-redux
Warning: even in this version of the test you can't actually see the real difference because cloning the array takes up most of the test time. It still shows that splice is the fastest way to clear the array (not taking [] into consideration because while it is the fastest it's not actually clearing the existing array).
Array.prototype.clear = function() {
this.length = 0;
};
And call it: array.clear();
In case you are interested in the memory allocation, you may compare each approach using something like this jsfiddle in conjunction with chrome dev tools' timeline tab. You will want to use the trash bin icon at the bottom to force a garbage collection after 'clearing' the array. This should give you a more definite answer for the browser of your choice. A lot of answers here are old and I wouldn't rely on them but rather test as in #tanguy_k's answer above.
(for an intro to the aforementioned tab you can check out here)
Stackoverflow forces me to copy the jsfiddle so here it is:
<html>
<script>
var size = 1000*100
window.onload = function() {
document.getElementById("quantifier").value = size
}
function scaffold()
{
console.log("processing Scaffold...");
a = new Array
}
function start()
{
size = document.getElementById("quantifier").value
console.log("Starting... quantifier is " + size);
console.log("starting test")
for (i=0; i<size; i++){
a[i]="something"
}
console.log("done...")
}
function tearDown()
{
console.log("processing teardown");
a.length=0
}
</script>
<body>
<span style="color:green;">Quantifier:</span>
<input id="quantifier" style="color:green;" type="text"></input>
<button onclick="scaffold()">Scaffold</button>
<button onclick="start()">Start</button>
<button onclick="tearDown()">Clean</button>
<br/>
</body>
</html>
And you should take note that it may depend on the type of the array elements, as javascript manages strings differently than other primitive types, not to mention arrays of objects. The type may affect what happens.
Use a modified version of Jan's initial suggestion:
var originalLength = A.length;
for (var i = originalLength; i > 0; i--) {
A.pop();
}
Terser:
for (let i = A.length; i > 0;A.pop(),i--) {}
Or here's another take:
while(!A[Symbol.iterator]().next().done)A.shift()
A.splice(0);
I just did this on some code I am working on. It cleared the array.
If you use constants then you have no choice:
const numbers = [1, 2, 3]
You can not reasign:
numbers = []
You can only truncate:
numbers.length = 0
To Empty a Current memory location of an array use: 'myArray.length = 0' or 'myArray.pop() UN-till its length is 0'
length : You can set the length property to truncate an array at any time. When you extend an array by changing its length property, the number of actual elements increases.
pop() : The pop method removes the last element from an array and returns that returns the removed value.
shift() : The shift method removes the element at the zeroeth index and shifts the values at consecutive indexes down, then returns the removed value.
Example:
var arr = ['77'];
arr.length = 20;
console.log("Increasing : ", arr); // (20) ["77", empty × 19]
arr.length = 12;
console.log("Truncating : ", arr); // (12) ["77", empty × 11]
var mainArr = new Array();
mainArr = ['1', '2', '3', '4'];
var refArr = mainArr;
console.log('Current', mainArr, 'Refered', refArr);
refArr.length = 3;
console.log('Length: ~ Current', mainArr, 'Refered', refArr);
mainArr.push('0');
console.log('Push to the End of Current Array Memory Location \n~ Current', mainArr, 'Refered', refArr);
mainArr.poptill_length(0);
console.log('Empty Array \n~ Current', mainArr, 'Refered', refArr);
Array.prototype.poptill_length = function (e) {
while (this.length) {
if( this.length == e ) break;
console.log('removed last element:', this.pop());
}
};
new Array() | [] Create an Array with new memory location by using Array constructor or array literal.
mainArr = []; // a new empty array is addressed to mainArr.
var arr = new Array('10'); // Array constructor
arr.unshift('1'); // add to the front
arr.push('15'); // add to the end
console.log("After Adding : ", arr); // ["1", "10", "15"]
arr.pop(); // remove from the end
arr.shift(); // remove from the front
console.log("After Removing : ", arr); // ["10"]
var arrLit = ['14', '17'];
console.log("array literal « ", indexedItem( arrLit ) ); // {0,14}{1,17}
function indexedItem( arr ) {
var indexedStr = "";
arr.forEach(function(item, index, array) {
indexedStr += "{"+index+","+item+"}";
console.log(item, index);
});
return indexedStr;
}
slice() : By using slice function we get an shallow copy of elements from the original array, with new memory address, So that any modification on cloneArr will not affect to an actual|original array.
var shallowCopy = mainArr.slice(); // this is how to make a copy
var cloneArr = mainArr.slice(0, 3);
console.log('Main', mainArr, '\tCloned', cloneArr);
cloneArr.length = 0; // Clears current memory location of an array.
console.log('Main', mainArr, '\tCloned', cloneArr);
I'm surprised no one has suggested this yet:
let xs = [1,2,3,4];
for (let i in xs)
delete xs[i];
This yields an array in quite a different state from the other solutions. In a sense, the array has been 'emptied':
xs
=> Array [ <4 empty slots> ]
[...xs]
=> Array [ undefined, undefined, undefined, undefined ]
xs.length
=> 4
xs[0]
=> ReferenceError: reference to undefined property xs[0]
You can produce an equivalent array with [,,,,] or Array(4)

Copying a javascript array inside a class protoype function [duplicate]

This question already has an answer here:
Deep copying array of nested objects in javascript [duplicate]
(1 answer)
Closed 6 years ago.
I have this class on javascript:
function Node(board,x,y,t){
this.board = board;
this.x = x;
this.y = y;
this.playerTile = t;
this.oponentTile = getOponentTile(this.playerTile);
this.parent = null;
this.getChildren = getChildren;
};
and I´m using this function which copies the this.board variable (which is an array) to the tempBoard variable using slice()
var getChildren = function() {
if(this.x==-1 && this.y ==-1){
var moves = getAllValidMoves(this.board,this.playerTile);
}
else{
var tempBoard = this.board.slice();
makeMove(tempBoard,this.playerTile,this.x,this.y);
var moves = getAllValidMoves(tempBoard,this.playerTile);
}
var children = [];
for(var i = 0;i<moves.length;i++){
var currentMove = moves[i];
var currentBoard = this.board.slice();
if(this.x==-1 && this.y ==-1){
children.push(new Node(currentBoard,currentMove[0],currentMove[1],this.playerTile));
}
else{
makeMove(currentBoard,this.playerTile,this.x,this.y)
children.push(new Node(currentBoard,currentMove[0],currentMove[1],this.oponentTile));
}
}
return children;
};
the problem is that after calling makemove() both the tempBoard and the this.board variables are being modified.
Is there any way I can copy an array without it´s reference?
.slice() makes a shallow copy of the array, not a deep copy.
That means that if you have an array of objects and you use .slice() to make a copy of the array, it gives you a new array that is a first level copy. But the new array points to all the same objects as the first array. You can rearrange the copied array or remove elements from it and that will not affect the first array.
But, if you modify the objects in the array, it is the same objects in both arrays so making a modification to any object in the array (such as changing a property on the object) will be seen in both arrays.
If you want a full copy of the array and it's contents, then you have to make a deep copy which is a bit more complicated and can slightly depend upon what exactly you have in the array.
There are many different ways to make a deep copy. You can read about many of them in these references:
How do I correctly clone a JavaScript object?
Copying an array of objects into another array in javascript (Deep Copy)
Copying array by value in JavaScript
If you are guaranteed not to have any circular references in your array (where one object points to another which points back to it), then the simplest way to make a copy is this:
var tempBoard = JSON.parse(JSON.stringify(this.board));
maybe help
function copy(data){
var result = JSON.stringify(data);
return JSON.parse(result);
}

Strange behavior in jQuery loop with arrays

I am having an issue with the following code:
var samount = [{value:100, name:'USD'},
{value:200, name:'USD'},
{value:100, name:'USD'}];
var object1 = new Array;
objects1 = samount;
var categories1 = new Array();
var groupedObjects1 = [];
var output1 = '';
var i = 0;
console.log(samount);
_.each(objects1,function(obj){
var existingObj;
if($.inArray(obj.currency,categories1) >= 0) {
existingObj = _.find(objects1,function(o){return o.currency === obj.currency;});
existingObj.value += obj.value;
} else {
groupedObjects1[i] = obj;
categories1[i] = obj.currency;
i++;
}
});
console.log(samount);
console.log(groupedObjects1);
The problem is that I do not want that samount variable to change after looping, so I have done this:
var object1 = new Array;
objects1 = samount;
The goal of this script is to sum up all values from the same currencies, but still to not mess with the initial array.
But it still changes the initial Array. Could anyone help me with this error?
Copy the array with slice
var objects1 = samount.slice(0);
Arrays and object are passed by "reference" (not really, but doesn't matter here), so when assigning an array to a new variable, all you get is a reference to the same array, not a new array.
You need to deep copy the initial array instead of affecting it.
var samount = [{value:100, name:'USD'},
{value:200, name:'USD'},
{value:100, name:'USD'}];
var object1 = $.extend(true, [], samount);
You were doing an affectation (i.e. 2 variables pointing to the same object) where you needed a copy (2 variables pointing to 2 different objects)
The problem is you're not creating a copy with
objects1 = samount
In most OO languages, you can only copy primitive types so int, strings characters etc, but not objects.
This is somewhat similar in javascript in the sense that {} is an object, same as Array or [].
So if you want to copy an object you'd have to iterate over every element of that object till you find an primitive type. This can be tedious and quite hard even (http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/) this is called a deep copy.
But in this case a shallow copy (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) is enough
var objects1 = samount.slice(0);

creating multi-dim arrays (JS)

I was trying to create a 3-dimensional array and couldn't find an easy way to do it.
array = [[[]]];
or
array = [][][];
or
array = []; array[] = []; array[][] = [];
would for example not work. (the console'd say the second array is 'undefined' and not an object, or for the second and third example give a parse error).
I cannot hard-code the information either, as I have no idea what the indexes and contents of the array are going to be (they are created 'on the fly' and depending on the input of a user. eg the first array might have the index 4192). I may have to create every array before assigning them, but it would be so much easier and faster if there's an easier way to define 3-dimensional arrays. (there'll be about 2 arrays, 25 subarrays and 800 subsubarrays total) every millisecond saves a life, so to say.
help please?
JavaScript is dynamically typed. Just store arrays in an array.
function loadRow() {
return [1, 2, 3];
}
var array = [];
array.push(loadRow());
array.push(loadRow());
console.log(array[1][2]); // prints 3
Since arrays in javascript aren't true arrays, there isn't really a multidimensional array. In javascript, you just have an arrays within an array. You can define the array statically like this:
var a = [
[1,2,3],
[4,5,6],
[7,8,9]
];
Or dynamically like this:
var d = [];
var d_length = 10;
for (var i = 0;i<d_length;i++) {
d[i] = [];
}
UPDATE
You could also use some helper functions:
function ensureDimensions(arr,i,j,k) {
if(!arr[i]) {
arr[i] = [];
}
if(!arr[i][j]) {
arr[i][j] = [];
}
}
function getValue(arr,i,j,k) {
ensureDimensions(i,j,k);
return arr[i][j][k];
}
function setValue(arr,newVal,i,j,k) {
ensureDimensions(i,j,k);
arr[i][j][k] = newVal;
}

Categories