2

As the question states I have an array which may or may not contain empty elements. I want to reverse this array. After using the reverse operation all the empty elements are made non-empty and thus, can be iterated. Why is this even allowed and how can I preserve the elements in the reversed array?

const originalArray = new Array(5);
originalArray[2] = "a";
originalArray.push("b");
originalArray.push("c");

const reversedArray = Array.from(originalArray).reverse();

console.log('');
console.log('original');
originalArray.forEach((el, index) => console.log('el: ', el, ' index: ', index));

console.log('');
console.log('reversed');
reversedArray.forEach((el, index) => console.log('el: ', el, ' index: ', index));

/*

Output is as follows:
original:
el:  a  index:  2
el:  b  index:  5
el:  c  index:  6

reversed:
el:  c  index:  0
el:  b  index:  1
el:  undefined  index:  2
el:  undefined  index:  3
el:  a  index:  4
el:  undefined  index:  5
el:  undefined  index:  6

I would have expected that there are still empty elements in the reversed array.

*/
AGoranov
  • 2,114
  • 3
  • 15
  • 27
  • 2
    Does [this question](https://stackoverflow.com/questions/50326047/whats-the-difference-between-empty-items-in-a-javascript-array-and-undefined) answer your question? – Etheryte Mar 08 '23 at 14:23
  • 3
    In short, the problem is not `reverse()`, the step you want to investigate is `Array.from()`. To see what I mean, check the output of your snippet without calling `reverse()`. – Etheryte Mar 08 '23 at 14:24
  • 2
    please add the wanted result. – Nina Scholz Mar 08 '23 at 14:27
  • @Etheryte this does not solve my problem. – AGoranov Mar 08 '23 at 14:32
  • @NinaScholz I just added :) – AGoranov Mar 08 '23 at 14:34
  • 1
    As @Etheryte alludes to, and [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) state early and explicitly, "`Array.from()` never creates a sparse array. If the `arrayLike` object is missing some index properties, they become `undefined` in the new array." – Darryl Noakes Mar 08 '23 at 14:35
  • And for `slice()`: "The `slice()` method preserves empty slots. If the sliced portion is sparse, the returned array is sparse as well." – Darryl Noakes Mar 08 '23 at 14:37

3 Answers3

3

Use .slice()

Replace

const reversedArray = Array.from(originalArray).reverse();

with

const reversedArray = originalArray.slice().reverse();

This will create a new array which retains the curious "gappiness" of your original array, but in the reverse pattern of course.

ProfDFrancis
  • 8,816
  • 1
  • 17
  • 26
  • 3
    While this offers a solution, it doesn't explain the nature of the problem nor why the original code doesn't work as expected. – Etheryte Mar 08 '23 at 14:26
  • "Why is this even allowed and _how can I preserve the elements in the reversed array_?" I can't answer the philosophical question, but at least I gave the solution you specifically asked for. 8-) – ProfDFrancis Mar 08 '23 at 18:33
0

Various ways to create shallow/deep copies of array work differently

const originalArray = new Array(5);
originalArray[2] = "a";
originalArray.push("b");
originalArray.push("c");

Array.from(originalArray)
//> [undefined, undefined, 'a', undefined, undefined, 'b', 'c']

[...originalArray]
//> [undefined, undefined, 'a', undefined, undefined, 'b', 'c']

originalArray.slice()
//> [empty × 2, 'a', empty × 2, 'b', 'c']

originalArray.map(v => v)
//> [empty × 2, 'a', empty × 2, 'b', 'c']

originalArray.filter(() => true)
//> ['a', 'b', 'c']

originalArray.concat()
//> [empty × 2, 'a', empty × 2, 'b', 'c']

JSON.parse(JSON.stringify(originalArray))
//> [null, null, 'a', null, null, 'b', 'c']

This is due to the way each method treats the passed object, for instance as an array or an iterable. You could use the ones which preserve the sparse array as-is.

Shreevardhan
  • 12,233
  • 3
  • 36
  • 50
0

As @Eureka said, you can use the slice() method instead of Array.from():

const reversedArray = originalArray.slice().reverse();

This will retain the sparseness of the array.


The output you are seeing is because of the behavior of Array.from() when dealing with sparse arrays. From the description in the MDN docs:

Array.from() never creates a sparse array. If the arrayLike object is missing some index properties, they become undefined in the new array.

Using Array.prototype.slice() gives the output you want, as its behavior is different:

The slice() method preserves empty slots. If the sliced portion is sparse, the returned array is sparse as well. (docs)

This difference in behavior is because of the semantic and functional difference between from() and slice(): Array.from() creates a new array from iterable objects and array-like objects, while the slice() method copies a portion of an array into a new array.

More specifically, slice() reads the properties in the specified range, and copies those to a new array:

The slice() method reads the length property of this. It then reads the integer-keyed properties from start to end and defines them on a newly created array.

Array.from(), on the other hand, works something like this when given an array-like object (an object with the length property and indexed elements):

  1. Read the normalized length of the array-like object.
  2. Construct a new array object, passing in the length to the constructor.
  3. For every index up to the length, set that index in the new array object to the value of that index in the original array-like object. Any empty slots will give undefined, and thus be non-empty in the new array.
  4. Run the map function on the new array, if one is passed.

(The Array.from() explanation is my understanding based on the docs and experience, but should give the gist of it.)

Darryl Noakes
  • 2,207
  • 1
  • 9
  • 27