Proposal: Array.prototype.unique


#1

For a long time I have wanted an Array.prototype.unique() method.

This method would return a unique set of elements contained in an Array.

I would easily see this signature: Array.prototype.unique(sorted, callback)Array.prototype.unique(callback), where:

  • sorted is a boolean (optional, default=false), indicating whether the Array is already sorted or not for optimizations. If it is wrongly true, an exception should be thrown
  • callback is a function (optional too) that is given two elements of the array (like the callback of Array.prototype.sort) and is expected to return true if they are considered as equal.

If the callback is not provided, the function would run as if the provided callback would return the result of a strict equality (i.e. callback = function(a, b) { return a === b; };

Even though having a unique Array is already possible in EcmaScript 6 using [...new Set(...myArray)], or using Array.prototype.filter() with the appropriate callback, I think this function would be more convenient, more concise and easier to read.

What do you think?


Edit: In retrospection, maybe the assertion that the Array is sorted could be made by default and be rejected if an element is expected to be put before its predecessor. Therefore, the sorted parameter is not necessary. Moreover, checking that the Array is sorted would have been hard to spec with the callback.

Florent


#2

Like you said, you can already do all this using a combination of Array, and Set. I don’t see any additional need for this.


#3

I see some reasons:

  1. Even though the Set is (relatively) easy to use to set an array (using [...new Set(array)]), the readability is not nice at least for new / occasional JS programmers, and is pretty slow (at least using Firefox implementation compared to the use of Array.prototype.filter). Moreover, it doesn’t support callbacks
  2. Using Array.prototype.filter could be an option, but that’s something which is (my guess) recurrent to use and redefined several times.
  3. There would probably be optimizations if it is embedded by default, which would be neat especially for large arrays
  4. It would be user-friendly, especially for occasional developers.

Here is an equivalent of what I have in mind (except that it should modify the array itself, to be consistent with Array.prototype.sort):

Array.prototype.unique = function(callback, thisArg) {
  let isSorted = !callback;
  return this.filter((x, i, arr) => {
    if (isSorted)
    {
      let prev = arr[i-1]
      if (prev && prev > x)
        isSorted = false;
      else
        return prev !== x;
    }
    return callback ? 
      arr.findIndex(callback.bind(thisArgs || null, x)) === i :
      arr.indexOf(x) === i;
  });
}

There could be some more optimizations (especially if we stop finding an index at the current element).

Example of use:

console.log([1,1,1,1,2,3,4,4,4,5].unique());
console.log([1,2,3,1,2,3,1,1,1,2,3,4].unique());
console.log([
      {a:1, b:2}, 
      {a:2, b:3}, 
      {a:3, b:4}, 
      {a:1, b:2}, 
      {a:2, b:3}, 
      {a:3, b:4}, 
      {a:1, b:2},
      {a:1, b:2},
      {a:1, b:2},
      {a:2, b:3},
      {a:3, b:4},
      {a:4, b:5}
].unique((x, y) => x.a === y.a));

Florent