Moving zeros to the end of an array in JavaScript

Published on in JavaScript and TypeScript

Let's learn about anti-symmetry by solving this interview puzzle: "Move all zeros of an array of integers to the end while maintaining the relative order of all non-zeros."

Example:

moveZeros([1, 3, 0, 111, 57, 0, 5, 0, 9])
//=>      [1, 3,    111, 57,    5,    9, 0, 0, 0]

Ok, ez because there's a great visual mnemonic for an array sorter's return values, so we can simply™ sort() the values:

const moveZeros = (numbers: number[]) =>
  numbers.toSorted((a, b) => (a === 0 ? 1 : 0))

moveZeros([1, 3, 0, 111, 57, 0, 5, 0, 9])
//=>      [1, 3,    111, 57,    5,    9, 0, 0, 0]

I used the new toSorted() method to avoid mutating the original array.

Seems to work with the test input, but might break with other inputs, because the comparator function is not anti-symmetric. From sort() on MDN:

[I]f a comparator only returns 1 and 0, or only returns 0 and -1, it will not be able to sort reliably because anti-symmetry is broken.

So we need to also check if b === 0:

const moveZeros = (numbers: number[]) =>
  numbers.toSorted((a, b) => (a === 0 ? 1 : b === 0 ? -1 : 0))

Nested ternaries are icky though, plus returning 1 or -1 is not very clear without comments.

Final solution:

const moveZeros = (numbers: number[]) =>
  numbers.toSorted((a, b) => {
    if (a === 0) return +1 //   a->
    if (b === 0) return -1 // <-a
    return 0 // Keep order
  })

Though I have to admit that Juhani Kumara's solution using reduceRight is annoyingly clever!

const move0 = (arr) =>
  arr.reduceRight((acc, cur) => (acc[cur ? 'unshift' : 'push'](cur), acc), [])