32

I have this code:

var ar = [10,7,8,3,4,7,6];

function isin(n,a){
  for (var i=0;i<a.length;i++){
    if (a[i]== n) {
      var b = true;
      return b;
    } else {
      var c = false;
      return c;
   }
  }
}

function unique(a){
  var arr = [];
  for (var i=0;i<a.length;i++){
    if (!isin(a[i],arr)){
      arr.push(a[i]);
    }
  }

 return arr;
}

alert(unique(ar));

In this code, I try to create new unique array (without duplicates) out of the original one. But I still get the original array! Where's my mistake?

Jonik
  • 80,077
  • 70
  • 264
  • 372
DrStrangeLove
  • 11,227
  • 16
  • 59
  • 72

18 Answers18

81

Or for those looking for a one-liner (simple and functional):

var a = ["1", "1", "2", "3", "3", "1"];
var unique = a.filter(function(item, i, ar){ return ar.indexOf(item) === i; });
Josh Mc
  • 9,911
  • 8
  • 53
  • 66
  • This is the correct answer because it uses a standard and built-in function rather than home-rolled solution. – erapert Feb 09 '17 at 18:42
  • Brilliant answer. Especially the ar (which can also be a I think) compared to the first match in the array. – Sander Aug 15 '18 at 11:56
  • @erapert `new Set()` in my answer below is built-in (or built-in if you don't have to support IE or ancient Android browsers). But I feel `Set()` is hindered b/c it is very hard to google (bad name for SEO). This answer, if your list ever reaches 1000 items this could have 100k-1mil comparison operations (the work done inside `indexOf()`). That is a lot of CPU for something that could be O(n) (a few operations per item). – yzorg Jul 02 '21 at 14:47
  • @erapert The "inline" version of my `unique()` is just: `[...new Set(list)]`. – yzorg Jul 02 '21 at 14:53
48

Using a plain array and returning the keys of associative array (containing only the "unique" values from given array) is more efficient:

function ArrNoDupe(a) {
    var temp = {};
    for (var i = 0; i < a.length; i++)
        temp[a[i]] = true;
    var r = [];
    for (var k in temp)
        r.push(k);
    return r;
}

$(document).ready(function() {
    var arr = [10, 7, 8, 3, 4, 3, 7, 6];
    var noDupes = ArrNoDupe(arr);
    $("#before").html("Before: " + arr.join(", "));
    $("#after").html("After: " + noDupes.join(", "));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="before"></div>
<div id="after"></div>

Note: The function does not preserve the order of the items, so if this is important use different logic.

As of IE9 and on all other modern browsers (e.g. Chrome, Firefox) this can become even more efficient by using the Object.keys() method:

function ArrNoDupe(a) {
    var temp = {};
    for (var i = 0; i < a.length; i++)
        temp[a[i]] = true;
    return Object.keys(temp);
}

$(document).ready(function() {
    var arr = [10, 7, 8, 3, 4, 3, 7, 6];
    var noDupes = ArrNoDupe(arr);
    $("#before").html("Before: " + arr.join(", "));
    $("#after").html("After: " + noDupes.join(", "));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="before"></div>
<div id="after"></div>

Thanks wateriswet for bringing this to my attention. :)

Community
  • 1
  • 1
Shadow The GPT Wizard
  • 66,030
  • 26
  • 140
  • 208
  • how does it work in your example?? Can't associative array contain duplicates?? how does it filter?? – DrStrangeLove Aug 04 '11 at 11:40
  • 1
    No, the associative array keys are unique and that's the "heart" of my approach. There is no "filter".. I'm just assigning the array items as keys of associative array then read the keys back to new array. – Shadow The GPT Wizard Aug 04 '11 at 11:53
  • 1
    This is friggin awesome! – Daniel Nov 12 '13 at 13:52
  • If you return Object.keys(temp) instead of cycling through all the keys and creating a new array you can speed it up even more! – Joe Thomas May 09 '16 at 23:19
  • @wateriswet well, I wasn't aware of that back when writing the answer, and it would fail for IE8 and below - you'll be surprised how many people still using those ancient versions. Anyway, worth adding this to the answer, will try doing it soon. Thanks! :) – Shadow The GPT Wizard May 10 '16 at 07:30
17

You could use the new native new Set(list) object in ES6/ES2015. (i.e. Babel, Typescript, or those lucky enough that all target browsers support ES2015).

// I never use this, because it's an iterator, not an array
let s = new Set(list);

or, if you want to chain to array helpers use the new ... spread operator in ES6/ES2015 to spread it into an array:

const unique = (list) => {
  return [...new Set(list)];
}

You need an array to chain methods like sort():

const convertText = (textToConvert) => {
  let list = unique(textToConvert.split(/\r?\n/g))
    .sort() // this will error if using uniqueAsIterator() version...
    .filter(x => x != "NULL");
  return list;
}
yzorg
  • 4,224
  • 3
  • 39
  • 57
10

In addition to usage of filter respond by Josh Mc, you can make it crazy shorter in es6 with arrow function utility;

const a = ["1", "1", "2", "3", "3", "1"];
let unique = a.filter((it, i, ar) => ar.indexOf(it) === i);
// unique = [1, 2, 3]
Burak Tokak
  • 1,810
  • 1
  • 20
  • 27
7

Edit: note the warning by Daniel. Considering that, and what the official docs say (below), maybe using this is not a great idea after all!


If you happen to use jQuery, its unique() function does this:

var ar = [1, 2, 1, 2, 2, 3];    
ar = $.unique(ar);
console.log(ar);  // [3, 2, 1] 

The documentation says:

Note that this only works on arrays of DOM elements, not strings or numbers.

...but when I tested this with jQuery 1.9.1, it does work for strings and numbers too. Anyway, double-check that it works, especially if using older jQuery.

Community
  • 1
  • 1
Jonik
  • 80,077
  • 70
  • 264
  • 372
  • 2
    I've experienced that it works with a certain amount of numbers but at some point stops working without any obvious reason. So use with caution. – Daniel Nov 12 '13 at 13:41
  • Thanks for the warning @Daniel ! How many values did you test for, if you can remember ? –  Nov 07 '14 at 16:58
  • @kiran.koduru I don't really remember. The array sizes weren't that high. Maybe between 20 and 300. Anyway, I would suggest using the method provided in the accepted answer: http://stackoverflow.com/a/6940176/587320. I've also added an CoffeeScript equivalent in case someone needs it: http://stackoverflow.com/a/26827148/587320 – Daniel Nov 09 '14 at 10:21
3

Because your isin method returns true or false after examining the first element.

change it to this:

function isin(n,a){
  for (var i=0;i<a.length;i++){
    if (a[i]== n){
    return true;

    }
  }
  return false;
}
Jamiec
  • 133,658
  • 13
  • 134
  • 193
2

And with some ES5 fun...

function uniqueArray(array) {
    var temp = array.reduce(function(previous, current) {
        previous[current] = true;
        return previous;
    }, {});

    return Object.keys(temp);
}
theGecko
  • 1,033
  • 12
  • 22
2

You should use indexOf instead of your isIn function:

function unique(a){
  var arr = [];
  for (var i=0;i<a.length;i++){
    if ( arr.indexOf(a[i]) == -1){
        arr.push(a[i]);
    }
}
F-A
  • 643
  • 3
  • 10
1

The way I did it was to use the array "backwards", so when I push to it I use the key instead of the value, like this:

var arr = [];
$('input[type=checkbox]', SearchResults).each( function() {
    if( $(this).is(':checked') )
        arr[ $(this).data('client_id') ] = true;
}

Then I look at the keys rather than the values.

david_nash
  • 678
  • 7
  • 14
1

Based on the accepted answer. Here is a CoffeeScript equivalent:

unique = (a) ->
  temp = {}
  for i in [0...a.length]
    temp[a[i]] = true
  r = []
  for k of temp
    r.push(k)
  return r
Community
  • 1
  • 1
Daniel
  • 4,082
  • 3
  • 27
  • 46
1

I know I'm somewhat late to answer this. But none of the answers have what I would do. I like using splice for this sort of thing. Here's a really simple script that'll do exactly what you need:

function unique(originalArray){
    var ar = originalArray.slice(0);//Make a copy of the array and store it in ar
    var i = ar.length;
    while(i--){  //Iterate through the array
        if(ar.indexOf(ar[i],i+1)>-1){  //If the array has a duplicate
            ar.splice(i,1);  //Remove that element!
        }
    }
    return ar; //Return the new, more unique array
}
Joe Thomas
  • 5,807
  • 6
  • 25
  • 36
1

Where's my mistake??

Right here:

... else {
      var c = false;
      return c;
   }

This causes the isin function to false if n doesn't match the first element in the array. The loop-body will always return a value before progressing to the next element.

Remove the else-clause and move return false to the bottom of the method:

function isin(n,a){
    for (var i=0;i<a.length;i++) {
        if (a[i] == n)
            return true;

    return false;
}

Note that the isin method can be implemented immediately (or even replaced by) a call to indexOf.

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • 1
    Works for arrays too :-) Here's [a link](http://www.tutorialspoint.com/javascript/array_indexof.htm) explaining it. – aioobe Aug 04 '11 at 11:13
  • Does not work for IE browsers of course, but there is [workaround](http://stackoverflow.com/questions/1744310/how-to-fix-array-indexof-in-javascript-for-ie-browsers). – Shadow The GPT Wizard Aug 04 '11 at 11:56
1

You can use the Spread Operator and take advantage of the uniqueness offered by Sets.

const ar = [10, 7, 8, 3, 4, 7, 6];
const arUnique = [...new Set(ar)];
0

I've done a similar search, I found this answer really good:

Unique values in an array [duplicate]

You can re-use those function on other code. This is also good approach where it is compatible with many browsers.

Cheer.

Community
  • 1
  • 1
Strawhero
  • 1
  • 2
0

Simplest version I know: Array.from(new Set(duplicateArray))

emcee22
  • 1,851
  • 6
  • 23
  • 32
0

Expanding on Jamiecs' answer above, here's an Array.prototype. of the same.

var arr = [1, 1, 1, 2, 5, 6, 4, 5 , 1];

Array.prototype.uniqueArr = function() {
var arr = [];
for (var i=0;i<this.length;i++) {
  if ( arr.indexOf(this[i]) == -1 ) {
    arr.push(this[i]);
  }
}
return arr
}

var arr = arr.uniqueArr();

console.log(arr);
Ste
  • 1,729
  • 1
  • 17
  • 27
0

Using Filter Function

const getUnique = (arr) => {
  return arr.filter((val, i, _self) => {
  return _self.indexOf(val) === i;
 });
}
let arr = [1,2,1,3,4,2];
console.log(getUnique(arr)); // [ 1, 2, 3, 4 ]
0

If you are okay with/already using lodash, then uniq function of Array is the one you are looking for.

_.uniq([2, 1, 2]);
// => [2, 1]
AnandShiva
  • 906
  • 11
  • 22