Array.length longer than array - javascript

I am getting some strange behaviour out of JavaScript array.length. In particular, I have an array that returns length as 3, when there is only one element in the array, at index 0. I've read this question/answer dealing with incorrect values returned by array.length, but it doesn't answer my question because my array doesn't seem to be associative.
Here's a screenshot of my console, demonstrating the odd behaviour.
The code is throwing an error because I'm looping over the first array using array.length and the code is trying to acccess the second and third elements it thinks should be in the array, and not finding them. Other entries in the database seem to not have this problem (see the second array in the screenshot).
So here's the question: Why is array.length in the first array 3 instead of 1?

The length Array property in javascript is writable from anyone and is not a reliable source to find the number of elements in the array.
Usually, it is safe to assume that the array has length elements in it, but sometime you can have different behaviours. This one is one of them.
var x = [1,2,3];
x.length = 5;
using a for construct will lead to some undefined values
for (var i = 0; i < x.length; i++) {
alert(x[i]);
}
using the forEach Array method would result (on Firefox at least) in the desired behaviour.
x.forEach(function(item) {
alert(item);
});
Note that, if you change the array length to a lesser value than the real value, the array would lose the extra elements and if you restore the original value the extra elements will be lost forever.
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length

Related

Does any change in an array gets to change the entire array?

I got into that question by thinking about sorting algorithms.
Does changing an element position inside an array would be the same, at the interpretation, compilation or run-time phases, to recreate the entire array with new elements?
Does it change radically from one language to another?
How does it work?
Imagining a specific case in JavaScript, for example:
let animals = ["monkey","zebra","banana","capybara"];
animals.splice(2,1); // returns ["banana"]
Would it be correct to state that the entire animals array was rewritten? Or is there another type of change type? How would that one and only change work, computation-wise?
It's stated in Javascript documentation that the .splice() method "takes" an array object, creates a new array without it and returns another one with it as an output, but how does it work?
I'm a begginner, so please have patience, and would like a good reading recommendation if possible to learn more about it. Sorry for the incovenience.
Does changing an element position inside an array would be the same, at the interpretation, compilation or run-time phases, to recreate the entire array with new elements?
I think you're asking this:
Does changing an element position inside an array recreate the entire array with new elements?
If you use the array indexer [], then no: the array is not recreated, it's mutated in-place.
If you use Array.prototype.splice, then no: the array is not recreated, it's mutated in-place.
But splice does return a new array containing the removed elements.
If you use Array.prototype.filter and/or Array.prototype.map, then yes: a new array containing only the filtered elements is returned (and the original input array is unmodified at all: i.e. nothing is mutated).
If you use Array.prototype.forEach to assign elements back to itself (which you shouldn't be doing anyway), then no: the array is mutated in-place.
Imagining a specific case in JavaScript, for example:
let animals = ["monkey","zebra","banana","capybara"];
animals.splice(2,1); // returns ["banana"]
Would it be correct to state that the entire animals array was rewritten?
No. That would be incorrect.
The correct thing to say is that "the animals array was mutated".
It's stated in Javascript documentation that the .splice() method "takes" an array object, creates a new array without it and returns another one with it as an output, but how does it work?
I don't know what "JavaScript documentation" you're referring to, but that is incorrect.
The only authoritative sources for JavaScript documentation are either the official ECMAScript specification or documentation provided by JavaScript engine vendors (such as Mozilla's Spidermonkey) documented on MDN.
Sources such as W3Schools are not authoritative.
...and MDN says this about splice (emphasis mine):
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
how does it work?
The inner-workings of the splice function are defined in the ECMAScript specification (section 23.1.3.29).
...which can be summarized as:
Given a splice invocation with arguments ( this: Array, start: int, deleteCount: int );...
Create a new array to hold deleteCount items: deletedItems.
Copy elements in this from index start to start + deleteCount into deletedItems.
Copy elements in this from start + deleteCount to this.length upwards to fill the gap of removed items.
Shrink the length to this.length - deleteCount.
In TypeScript, something like this (ignoring handling of negative arguments):
function splice<T>( this: T[], start: int, deleteCount: int ): T[] {
const removedItems: T[] = [];
const tailIdx = start + deleteCount;
for( let i = start; i < tailIdx; i++ ) {
removedItems.push( this[i] );
}
for( let i = tailIdx, f = start; i < this.length; i++, f++ ) {
this[i] = this[f];
}
this.length = this.length - deleteCount;
}

Why array.forEach(() => { array.pop() }) would not empty the array

While on the nodejs REPL I was trying to clean up an array defined as const array = [...] and just found out that using array.forEach(() => /pop|shift/()) would not work. After such expression the array will still hold values in it.
I'm well aware of better methods to clean the array, like array.splice(0), but I'm really curious about this behavior as seems counter-intuitive, at least for me.
Here's the test:
const a = [1, 2, 3]
a.forEach(() => {
a.shift()
})
console.log(a) // [ 3 ]
const b = [1, 2, 3]
b.forEach(() => {
b.pop()
})
console.log(b) // prints [ 1 ]
Notes
At first I was using arr.forEach(() => arr.pop()), so I though that one of the values was short-circuiting the forEach but wrapping the lambda in a body-block { .. } will also produce the same results.
The results are consistent across different node versions and browsers .. so it seems like it's well-defined behavior.
The quantity of leftover values, those still in the result array, change depending on the length of the input array, and seems to be Math.floor(array.length / 2)
The leftover values are always ordered accordingly to the /pop|shift/ method used, so some of the calls are actually changing the input array.
It also returns the same results by calling Array.prototype.forEach(array, fn)
Check out this quote from here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
If the values of existing elements of the array are changed, the
value passed to callback will be the value at the time forEach()
visits them; elements that are deleted before being visited are not
visited.
You're iterating from the beginning and removing the last element each iteration. This means you're moving forward 1, and reducing the length by 1, each iteration. Hence why you're ending up with floor(initialLength / 2) iterations. You're modifying the same array that you're forEaching, which as stated above means that you will not have the callback invoked for those pop'd elements.
Modifying an array while iterating over it is generally a bad idea. In fact, in Java, trying to do so would cause an exception to be thrown. But let's convert the forEach into an old-school for loop, and maybe you'll see the issue.
for (let i = 0; i < a.length; ++i) {
a.pop();
}
Is it clearer now what's going on? Each iteration you're shortening the length of the array by 1 when you pop the last element off. So the loop will end after iterating over half the elements -- because by then, it will have REMOVED half the elements, too, causing the value of i to be more than the current length of the array.
The same thing is happening when you use forEach: you're shortening the array with each iteration when you pop, causing the loop to terminate after only half the elements have been iterated. In other words, the iterator variable will move forward past the end of the array as the array shrinks.
.pop
Let's do this instead:
let arr = 'abcde'.split('');
arr.forEach((x,i,a) => {
console.log('before', x,i,a);
arr.pop();
console.log('after', x,i,a);
});
console.log(arr);
Your index is incrementing but your length is decrementing, so you're deleting the last elements when your index is in the first elements, thus the result where you delete the right half of the array.
.shift
Same: the iterating index goes one way, the length in another, so the whole things stop mid-work:
let arr = 'abcde'.split('');
arr.forEach((x,i,a) => {
console.log('before', x,i,a);
arr.shift();
console.log('after', x,i,a);
});
console.log(arr);

Do undefined elements in an array have an impact in JS? [duplicate]

This question already has answers here:
Deleting array elements in JavaScript - delete vs splice
(29 answers)
Closed 4 years ago.
I couldn't find a question that specifically targets the issue I'm having hence this question is being asked.
I have an array that holds 5 numbers:
var numbers = [0,1,2,3,4];
Once a number is clicked on the frontend (website), the number is removed from the array using the below code:
delete numbers[1];
This removes the correct number but leaves a space where the number was (the space is undefined). I believe this is causing an issue. After a number is removed from the array, I use another function to randomly pick any of the remaining numbers in the array however it sometimes fails. After much thought, I've realized it may be because there are empty spaces in the array after a number is removed and as a result, the code fails to work due to the undefined element.
Is my analogy correct or am I mistaken?
(I have also attempted to use the splice method to remove the number however that then causes an issue with the length of my array because if I later want to remove another number, it removes the wrong one due to the numbers moving around etc).
What you'd want to use is splice
In your specific case, numbers.splice(1,1)
You're correct that delete replaces one of the values in the array with undefined, and does not change the array length. Later on when you randomly choose an element from the array, you can wind up getting that undefined value, because it's still taking up a slot in the array:
var numbers = [0,1,2,3,4];
delete numbers[3];
console.log(numbers)
Instead use splice, which removes the item from the array completely:
var numbers = [0,1,2,3,4];
numbers.splice(3,1) /// remove one element starting at index 3
console.log(numbers)
if I later want to remove another number, it removes the wrong one due to the numbers moving around
You do need to choose one behavior or the other. If you need to preserve indexes as is, then continue to use delete, leaving the undefined values in the array, and rewrite your "choose one at random" function to never pick undefined values:
// start with some undefined values:
var numbers = [0, 1, undefined, undefined, undefined, 5]
var pickRandom = function(numbers) {
// make a copy of the array, removing undefined elements:
var definedValues = numbers.filter(function(item) {
return item !== undefined;
});
if (definedValues.length === 0) {return false}
//choose one at random:
return definedValues[Math.floor(Math.random() * definedValues.length)]
}
// test it:
console.log(pickRandom(numbers));
console.log(pickRandom(numbers));
console.log(pickRandom(numbers));
console.log(pickRandom(numbers));
(...but note that this suggests that a simple array is the wrong data structure to use here; you may be better off with an array of objects each with an explicit ID, so you can reference specific ones as needed without worrying about keeping the array index the same.)
If you mean to actually remove the element from the array, leaving your array with 4 elements, then you can use
numbers.splice(1);
This will remove the element in the index 1 from the array and return the section of the new array.

Array length Vs Number of values in Array

Recently i had to make an Array with values at large indexes (due to plugin constraints).
eg:
var names[100000] = "a";
var names[150000] = "b" ... and so on till 5 large indexes.
and in between all values are undefined names[100001] //undefined.
Now my doubt is Since the array has only 5 elements but if i do
names.length //it is 300001
its a large Array. I am not iterating this array nor i am running any loop through it. I will get the values directly through their defined indexes from the array.
So will this structure make any significant performance differences on the Browser or is it alright to use this as long as the number of values in the array is less irrespective of its indexes and no iteration is involved.
Thanks
The only thing that differentiates an array from a plain object is its length property and how it behaves (and a few array specific methods of course). The length value simply increases with certain operations, like setting a numeric property or pushing a new element. That's it in a nutshell. The array doesn't actually contain 100000 elements when you set the property 100000 to a value, all that's happening is that you're setting one property and the value of length is adjusted accordingly.
So, no, it won't have a lot of impact on performance, unless somebody actually iterates through the array using for (let i = 0; i < arr.length; i++).
You can create an array with the length, given by your plugin and work locally with an object to limit the iterations. After all your processing has been applied, you copy the values to the array and send it to the plugin's function.
Keep an array with the desired length as a buffer
var buffer = new Array(20000);
Internally work with an object
var data = {};
Assign values to the object
data[10001] = "foo";
Once transformations or data processing has been applied to the object, copy data to the buffer
for (key in data){
buffer[key] = data[key];
}
Send buffer to the plugin. And clear data, if desired.
By doing so, you will not iterate more, than the actual data you processed.

Can Array.splice() be used to create a sparse array by adding an element at an index beyond the last element of the array?

Can Array.splice() be used to create a sparse array by adding an element at an index beyond the last element of the array?" I need this because in some situations I just want to push onto the array, but in other situations I need to splice into the array. But trying to use splice to make the array sparse did not work, though in my particular situation I was able to implement some code to test whether to use splice, or just assign an array element at an index beyond the array's length.
No. The ECMAScript specification does not allow a relative start position greater than the array length. From ES2015 on Array.prototype.splice, step 7:
...let actualStart be min(relativeStart, len).
The variable actualStart is what's actually used for the splice algorithm. It's produced by the minimum of relativeStart (the first argument to the function) and len (the length of the array). If len is less than relativeStart, then the splice operation will use len instead the actual argument provided.
In practical terms, this means that you can only append values onto the end of arrays. You cannot use splice to position a new element past the length index.
It should be noted the length of the array is not necessarily the index of the last item in the array plus 1. It can be greater.
Then, you can't use splice to insert elements beyond the length of the array, but if you make sure the length is large enough, you can insert beyond the last index plus 1.
var arrSplice = ['what', 'ever'];
arrSplice.length = 10; // Increase the capacity
arrSplice.splice(10, 0, 'foobar'); // Now you can insert items sparsely
console.log(arrSplice.length); // 10
console.log(arrSplice[arrSplice.length - 1]); // foobar
Array.splice() cannot be used to create sparse arrays. Instead, if the index argument passed to Array.splice() is beyond the length of the array, it seems that the element just gets appended to the array as if Array.push() had been used.
/* How you might normally create a sparse array */
var arrNoSplice = ['foo', 'bar'];
arrNoSplice[10] = 'baz';
console.log(arrNoSplice.length); // 11
/* Demonstrates that you cannot use splice to create a sparse array */
var arrSplice = ['what', 'ever'];
arrSplice.splice(10, 0, 'foobar');
console.log(arrSplice.length); // 3
console.log(arrSplice[arrSplice.length - 1]); // foobar

Categories

Resources