0

I tried to phrase my question very carfully because it looks like a duplicate of jQuery function to get all unique elements from an array? and a bunch of other threads. I need to be very clear, I want to get all elements from an array that are not repeated at all. That is, given an array [1,2,3,4,5,1,2,3,5], it should return [4]. In that other thread, I was losing my mind because I didn't understand why they were using the word unique, which means "Being the only one of its kind; unlike anything else." and building functions that return numbers that have been repeated several times, and it was only while asking this question that I came to understand how they interpreted the question. Looking around the net, it appears that that's how everyone is interpreting it.

I feel very close with this code here:

  var myArr = [1,2,3,4,5,1,2,3,5];
  var unique = myArr.filter(function(value, index, self){ 
    return self.indexOf(value) != index;
  });
 console.log(unique);

Which returns an array with each value in the array except ones that do not get repeated. If I could just assign the removed elements to unique, I'd be set, but even reading the documentation on filter I am struggling to understand how it works.

I prefer vanilla js, as I'm coding in FreeCodeCamp's environment and I don't know if I can include libraries.

  • 3
    `return self.indexOf(value) === self.lastIndexOf(value);` – Jaromanda X Oct 05 '17 at 02:17
  • I guess the use of "unique" in those other questions refers to the desired *output* array containing only unique values, much like the "select distinct" feature in SQL. But yes, I can see why you got confused. – nnnnnn Oct 05 '17 at 02:22
  • as far as "language" goes ... I'd say "get elements that are unique" for what you want and "get unique elements" for what the others do :p – Jaromanda X Oct 05 '17 at 02:26

3 Answers3

2

Close ... sort of ... just check if indexOf is the same as lastIndexOf

var myArr = [1,2,3,4,5,1,2,3,5];
  var unique = myArr.filter(function(value, index, self){ 
    return self.indexOf(value) === self.lastIndexOf(value);
  });
console.log(unique);
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • I knew the solution would be this simple. If the first index of a value is also the last, then it is unique. I'm embarrassed I didn't think of it. Thank you. – Kyle Freeman Oct 05 '17 at 02:28
0

You are indeed very close, and @Jaromanda-X has given you the solution in his comment, return self.indexOf(value) === self.lastIndexOf(value);. You would use the line like so:

var myArr = [1,2,3,4,5,1,2,3,5];
  var unique = myArr.filter(function(value, index, self){ 
    return self.indexOf(value) === self.lastIndexOf(value);
  });
console.log(unique);

For reference, .indexOf returns the first index of the value in the array, and .lastIndexOf returns the last index of the value in the array.

0

Solutions that utilise indexOf will require a linear search for each element, so would have a performance complexity of O(N^2).

Here is a two step solution. It stores a list of numbers and their counts, and then, from this list, find the elements with a count of one.

let myArr = [1,2,3,4,5,1,2,3,5];
let counts = myArr.reduce((ht, v) => { ht[v] = (ht[v] || 0) + 1; return ht;} {});
let unique = Object.keys(counts).filter(k => counts[k] === 1).map(v => parseInt(v, 10));

This solution assumes that every item in myArr is a number. If the array was a mix of numbers and strings, and you wanted to preserve the type, you can modify it to this solution (thanks @JaromandaX):

let myArr = [1,2,3,4,5,'apple',1,2,3,5];
let counts = myArr.reduce((ht, v) => {
    ht[v] = ht[v] || {v, count:0};
    ht[v].count +=1;
    return ht;
}, {});
let unique = Object.keys(counts)
                   .filter(k => counts[k].count === 1)
                   .map(k => counts[k].v);

But what about an array like [1, 1, 2, "2"]? The above solution derives its key from the string representation of the object, so the number 2 and the string "2" would be seen as equivalent.

The solution to this is to generate the key using a combination of the type and the value.

let myArr = [1,2,3,4,5,'apple',1,"2",3,5];
let counts = myArr.reduce((ht, v) => {
    let k = `${typeof(v)};${v}`;
    ht[k] = ht[k] || {v, count:0};
    ht[k].count +=1;
    return ht;
}, {});
let unique = Object.keys(counts)
                   .filter(k => counts[k].count === 1)
                   .map(k => counts[k].v);
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • 1
    `.map(parseInt)` doesn't do what you need, given that `.map()` calls the supplied callback with *three* arguments... – nnnnnn Oct 05 '17 at 02:48
  • @nnnnn Good point! `map` would pass in the index as the second parameter, and `parseInt` would interpret that as the radix. That would be an awful bug to try and detect. I'll fix it now. – Andrew Shepherd Oct 05 '17 at 02:52
  • I think `.map(Number)` would be fine. Although either way note that this solution only works on arrays of numbers. If the array might contain mixed data types there'd be problems. – nnnnnn Oct 05 '17 at 02:54
  • @nnnnnn: Cool, I didn't know that you could call `Number(string)` to parse a string. – Andrew Shepherd Oct 05 '17 at 02:57
  • I am studying this solution because I want to understand, but I'm glad you mentioned that it only allows the use of numbers because I failed to mention that I was actually using strings and numbers. – Kyle Freeman Oct 05 '17 at 03:02
  • To convert strings to numbers i generally use the unary plus operator (`num = +stringVal`), but of course you can't pass an operator to `.map()`. Anyway, this two-step solution is particularly good if the values are all strings so that you don't need to `.map()`. However, you could do a variation using a `Map` instead of a plain object and it should work for all data types including objects. – nnnnnn Oct 05 '17 at 03:03
  • expanding your code, to maintain value and type of each item in the array - https://jsfiddle.net/kd3eqb6r/ – Jaromanda X Oct 05 '17 at 03:37
  • @JaromandaX: Thanks, I've incorporated that into the solution. We could make this even harder - have an array of `[1, 1, 2, "2"]`, and make it that `2` and `"2"` are interpreted as different values. – Andrew Shepherd Oct 05 '17 at 03:45
  • Well a `Map` object solves that: keys can be any type. – nnnnnn Oct 05 '17 at 05:25
  • @JaromandaX I've taken your solution and enhanced it even further. – Andrew Shepherd Oct 05 '17 at 05:52