Array.prototype.remove(item)

Isiah Meadows isiahmeadows at gmail.com
Fri Nov 10 10:38:44 UTC 2017


I find myself frequently using this code to remove an item from an array:

```js
var index = array.indexOf(item)
if (index >= 0) array.splice(index, 1)
```

That's nice, but there's a couple problems:

1. It only removes the first element, not all occurrences in the array
(which is usually what you want).
2. I know of no engine that fuses those calls, and it'd be rather
difficult to do so.

My proposal is this: add a new `Array.prototype.remove(item)` method
that basically does this:

- Accept a list of items to remove, passed as arguments.
- Remove all instances of those items from the array.
- Return `true` if any entries were removed, `false` otherwise.

And in the process, it should take care to do it in an optimized
fashion, avoiding the overhead of allocation, excess reads/writes, and
excess branching.

In case you're curious why I'm not using `Set`, here's why that's
ill-equipped in many cases:

- Sets take up a *lot* more memory than arrays, by necessity.
- Sets are stored typically in tree-like structures, making for poor
iteration performance.
- If you're frequently iterating, but infrequently adding/removing,
these two will quickly kill your performance.

It rarely comes up in day-to-day coding, but the difference is
substantial when you're writing lower-level data structures and
performance-sensitive code. It's probably down a similar vein to
`Array.prototype.copyWithin`, which was pretty much designed for
`memcpy` or `memmove`.

Here would be a polyfill of what I'd like to propose (it uses a
minimum of writes, by design, and it does everything in one pass):

```js
if (typeof Array.prototype.remove !== "function") {
    // Private utility
    const includes = (items, itemLength, entry) => {
        for (let j = 0; j < itemLength; j++) {
            if (items[j] === entry) return true
        }

        return false
    }

    Array.prototype.remove = function (item) {
        const oldLength = this.length
        const newLength = arguments.length
        // This is technically observably no different than
        const items = [...arguments]
        let newLength = 0

        for (let i = 0; i < oldLength; i++) {
            const entry = this[i]
            if (includes(items, newLength, entry)) {
                let newLength = i++

                while (i !== list.length) {
                    const entry = this[i]
                    if (!includes(items, newLength, entry)) {
                        this[newLength++] = entry
                    }
                    i++
                }

                this.length = newLength
                for (let i = newLength; i < oldLength; i++) delete this[i]
                return true
            }
        }

        return false
    }
}
```

If the variadic version wasn't accepted, there's also the non-variadic version:

```js
if (typeof Array.prototype.remove !== "function") {
    Array.prototype.remove = function (item) {
        const oldLength = this.length
        let newLength = 0

        for (let i = 0; i < oldLength; i++) {
            const entry = this[i]
            if (entry === item) {
                let newLength = i++

                while (i !== list.length) {
                    const entry = this[i]
                    if (entry !== item) this[newLength++] = entry
                    i++
                }

                this.length = newLength
                for (let i = newLength; i < oldLength; i++) delete this[i]
                return true
            }
        }

        return false
    }
}
```

-----

Isiah Meadows
me at isiahmeadows.com

Looking for web consulting? Or a new website?
Send me an email and we can get started.
www.isiahmeadows.com


More information about the es-discuss mailing list