5

Before people think this is the same as all these other answers on Stack Overflow regarding removing duplicates can you please look at what is returned as opposed to what I am looking to do.

I want to remove all elements that occur more than once. I have tried the following:

const a = ['Ronaldo', 'Pele', 'Maradona', 'Messi', 'Pele'];

const uniqueArray = a.filter(function(item, pos) {
  return a.indexOf(item) == pos;
})

console.log(uniqueArray)

I want my uniqueArray to be

['Ronaldo', 'Maradona', 'Messi'];
peter flanagan
  • 9,195
  • 26
  • 73
  • 127

5 Answers5

12

In short: keep the value if the position of the first occurrence of the element (indexOf) is also the last position of the element in the array (lastIndexOf).

If the indexes are not equal then the value is duplicated and you can discard it.

const a = ['Ronaldo', 'Pele', 'Maradona', 'Messi', 
           'Pele', 'Messi', 'Van Basten', 'Baggio', 
           'Messi', 'Seedorf', 'Maradona'];
    
const uniqueArray = a.filter(function(item) {
  return a.lastIndexOf(item) == a.indexOf(item);
});

console.log(uniqueArray);
/* output:  ["Ronaldo", "Van Basten", "Baggio", "Seedorf"] */

Another idea is to perform an array iteration, add the values as the keys of an object, count the occurrences and then keep the keys with a total of occurrences equal to 1.

const a = ['Ronaldo', 'Pele', 'Maradona', 'Messi', 
           'Pele', 'Messi', 'Van Basten', 'Baggio', 
           'Messi', 'Seedorf', 'Maradona'];
    
const obj = {};
const uniqueArray = [];

a.forEach((item) => {
  obj[item] = (typeof obj[item] === 'undefined')? 1 : ++obj[item];
});

for (let key in obj) {
  if (obj[key] === 1) {
    uniqueArray.push(key)
  }
}

console.log(uniqueArray);
/* output:  ["Ronaldo", "Van Basten", "Baggio", "Seedorf"] */

The complexity of this code is linear while the first solution is quadratic.

I made a benchmark on JSPerf and the last approach is ~11x times faster using an array of 535 items with 119 unique values.

Fabrizio Calderan
  • 120,726
  • 26
  • 164
  • 177
3

You can count the item in each iteration so that you can include that in the condition:

const a = ['Ronaldo', 'Pele', 'Maradona', 'Messi', 'Pele'];

const uniqueArray = a.filter(function(item, pos) {
  var c = a.filter(i => i==item).length;
  return a.indexOf(item) == pos && c <= 1;
})

console.log(uniqueArray);

Though the better solution would be to compare the index of the current array item with Array.prototype.indexOf() and Array.prototype.lastIndexOf():

const a = ['Ronaldo', 'Pele', 'Maradona', 'Messi', 'Pele'];

const uniqueArray = a.filter(function(item, pos) {
  return a.indexOf(item) === a.lastIndexOf(item);
})

console.log(uniqueArray);
Mamun
  • 66,969
  • 9
  • 47
  • 59
2

You can utilise a Map to count how many times each entry shows up and then only take the ones that only do so once.

const a = ['Ronaldo', 'Pele', 'Maradona', 'Messi', 'Pele'];

const uniqueArray = onlyUniqueItems(a);

console.log(uniqueArray)


function onlyUniqueItems(array) {
  const map = new Map();
  const result = [];
  
  array.forEach(item => {
    let amount = map.get(item) || 0;
    map.set(item, ++amount); //pre-increment and then set the amount
  });
  
  map.forEach((amount, item) => {
    if (amount == 1) {
      result.push(item);
    }
  });
  
  return result;
}

This will run in O(2n) as you will have to do two scans at most. You can achieve a similar result using a plain object and set key-values on it but a Map has the advantage of preserving the type of the items you had, whereas a plain object will convert all keys to strings:

const a = [1, 2, 3, 3, "4", "4", "5"];

const uniqueArray = onlyUniqueItems(a);
const uniqueArrayUsingObject = onlyUniqueItemsUsingObject(a);

console.log(uniqueArray)
console.log(uniqueArrayUsingObject)


function onlyUniqueItems(array) {
  const map = new Map();
  const result = [];
  
  //count occurences
  array.forEach(item => {
    let amount = map.get(item) || 0;
    map.set(item, ++amount); //pre-increment and then set the amount
  });
  
  //extract only the items that show once
  map.forEach((amount, item) => {
    if (amount == 1) {
      result.push(item);
    }
  });
  
  return result;
}


function onlyUniqueItemsUsingObject(array) {
  const map = {};
  const result = [];
  
  //count occurences
  array.forEach(item => {
    let amount = map[item] || 0;
    map[item] = ++amount; //pre-increment and then set the amount
  });
  
  //extract only the items that show once
  Object.entries(map).forEach(([item, amount]) => {
    if (amount == 1) {
      result.push(item);
    }
  });
  
  return result;
}
VLAZ
  • 26,331
  • 9
  • 49
  • 67
1

The naive way would be to check for more than one occurence:

   a.filter((el, i) => !(a.indexOf(el) !== i || a.indexOf(el, i) > -1)));

Or a more complicated one with multiple Sets (but O(n)):

  const found = new Set, values = new Set;

  for(const el of a)
   if(!found.has(el)) {
     found.add(el);
     values.add(el);
   } else if(values.has(el) {
     values.delete(el);
   }
 }

 const result = [...values.values()];
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
-1

Why not to use Set?

const theSet = new Set(a);
const values = set.values();
const uniqueArray = Array.from(values);
Fka
  • 6,044
  • 5
  • 42
  • 60
  • Because OP wants to ignore duplicate values *entirely* - if a value has a duplicate, don't include it at all. – VLAZ Oct 30 '18 at 14:49