-3

This might look like a duplicate question, but please don't assume this just because it looks similar to others, and be careful with easy answers unless you really understand the ramifications.

Here is a console dialog to demonstrate the problem:

> a = [0,1,2]
< >(3) [0,1,2]
> a.length
< 3
> a[99] = 99
< 99
> a.length
< 100
> delete a[99]
< true
> a.length
< 99

So this length property of arrays doesn't seem to follow any reasonable specification. Trailing sparse elements should be compacted out, but aren't.

I think as a result, the length property should never be used in code since it is semantically meaningless. (Instead findLastIndex(() => true) should be used anywhere a naive programmer woul previously use length.)

I know how to use the elegant forEach, some, every, find, filter, map, and reduce functions. And I know that filter doesn't preserve sparseness. I can use reduce to do a map which can drop elements or a filter which preserves sparseness.

How can we reset the array length property to something reasonable without copying the array?

There may be no answer at all, or rather, the answer may be "there is no way and the array length property is essentially meaningless."

E_net4
  • 27,810
  • 13
  • 101
  • 139
Gunther Schadow
  • 1,490
  • 13
  • 22
  • 1
    "Every Array has a non-configurable `length` property whose value is always a non-negative integral Number whose mathematical value is less than `2**32`. The value of the `length` property is numerically greater than the name of every own property whose name is an array index; whenever an own property of an Array is created or changed, other properties are adjusted as necessary to maintain this invariant." - [ES2023 spec](https://tc39.es/ecma262/#sec-array-exotic-objects). – Amadan Apr 30 '22 at 19:37
  • @Amadan thank you for providing the specification text about it. It is obvious that the authors are struggling to define their way around an arbitrary implementation property. The value is greater, so I could implement this property as a constant of 2^32 and meet the spec, which is proof that the property is useless. – Gunther Schadow Apr 30 '22 at 19:48
  • I could not find the spec for `delete`; but `splice` explicitly decreases `length` by the number of deleted elements, and increases by number of added elements ([spec](https://tc39.es/ecma262/#sec-array.prototype.splice), step 20). Treating `delete` as a special case of splice, `length = length -1 +0` makes sense. The `length` property is quite useful as it provides the upper limit to the indices, and in daily practice, especially with non-sparse arrays being the overwhelming use case, it is invaluable — far from useless. – Amadan Apr 30 '22 at 19:54
  • `length` will not be `2**32` because of other items: the constructor will set it to 0 if there are no elements at construction time ([spec](https://tc39.es/ecma262/#sec-array), step 4a), and it only increases via explicit assignment, by operations that add elements (in which case the operation increases `length` by the number of elements added), or creation of a new index (where, if the new index is >= `length`, `length` is set to one higher than the new index - [spec](https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc), step 2.j.i). It is hardly underspecified. – Amadan Apr 30 '22 at 19:58
  • 1
    https://love2dev.com/blog/javascript-remove-from-array/#delete-operator claims `delete` just sets that element to ``, that is, removes it without changing the indices of subsequent items, and doesn't change `length`. Formally `delete` removes a property from an object, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete – hpaulj Apr 30 '22 at 23:56

1 Answers1

1

Since nothing in JavaScript specification says length must be exactly one higher than the last index, your demand is not reasonable. In common use, dense arrays overwhelmingly outnumber sparse ones. Since JavaScript does not keep indices in an ordered structure, finding out which index is the last every time array contents change would incur a performance toll in a number of cases where this stronger invariant is not needed.

If you actually need to trim down an array to exclude the trailing non-elements, it is easy enough to do: find the last valid index, and shrink the array yourself:

const a = [0,1,2]
a[99] = 99
delete a[99]
a.length = a.findLastIndex(i => i in a) + 1;
console.log(a);        // [1, 2, 3]
console.log(a.length); // 3

Can it be slow, in case where length is large? Yes. This is precisely why this calculation is not done by default.

EDIT: findLastIndex is not present in all browsers, a workaround or a polyfill might be required.

EDIT2: Better yet, reduce can be employed, which does not call the predicate for the absent indices (and is also present in all current browsers):

const a = [0,1,2]
a[99] = 99
delete a[99]
a.length = a.reduce((l, x, i) => i, 0) + 1;
console.log(a);        // [1, 2, 3]
console.log(a.length); // 3
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • 1) I did not downvote you. 2) There are no invectives, I have been nothing but polite. I don't call you unreasonable, only your suggestion (because it is, due to the unsustainable performance impact). 3) `lastIndexOf(() => true)` does not work: `lastIndexOf` will iterate indices from 0 to `length - 1`, _not_ skipping sparse ones (at least in Chrome I am testing on). You need to actually test whether an index is present in the array or not. And even if it was useless, I can be a bit more lax about speed because this should be a rare operation (unlike the normal updates to `length`). – Amadan Apr 30 '22 at 20:38
  • There is lots more interesting to say about this findLastIndex and how it is implemented in the Google Chrome engine. All of that adds evidence that my question should be considered more carefully before dismissing it. The findLastIndex function is implemented extremely inefficient and I wonder if the spec forces this inefficiency?! – Gunther Schadow Apr 30 '22 at 20:48