3

Given an array of objects:

var arr = [
  {a: "foo", b: "bar", c: "baz" },
  {a: "foo", b: "bar", c: "qux" },
  {a: "foo", b: "bar", c: "baz" },
  {a: "foo", b: "bar", c: "qux" },
  {a: "bar", b: "foo", c: "qux" },
  {a: "bar", b: "qux", c: "foo" },
  {a: "bar", b: "foo", c: "qux" }
];

The array shall be filtered so only the unique objects remain:

var arr = [
  {a: "foo", b: "bar", c: "baz" },
  {a: "foo", b: "bar", c: "qux" },
  {a: "bar", b: "foo", c: "qux" },
  {a: "bar", b: "qux", c: "foo" }
];

For arrays of plain strings, I use

arr.filter(function (value, index, self) { 
    return self.indexOf(value) === index;
}

but this doesn't work on objects. All properties must be compared in my case. I guess some sort of deep comparision is needed?

silverwind
  • 3,296
  • 29
  • 31
  • You can concatenate each object value with a delimiter and can add it to a temporary array (Make sure index is same). Then you can compare it and remove it from main array. – Vigneswaran Marimuthu Apr 27 '15 at 10:41
  • http://jsfiddle.net/791gqw27/ – Andy Apr 27 '15 at 10:53
  • Whoever wants to reopen it: could you please tell the community why you think it's not a duplicate? – zerkms Apr 27 '15 at 10:56
  • @zerkms the duped question is for deep object comparision, which is likely needed, but doesn't contain a full answer to my question. – silverwind Apr 27 '15 at 11:19
  • @silverwind you already know how to filter don't you? If you know how to filter strings, how is it different from filtering objects? – zerkms Apr 27 '15 at 11:23
  • @zerkms I'm not sure how the function supplied to `Array.prototype.filter` should look given I already have the `deepCompare` function. – silverwind Apr 27 '15 at 11:31
  • @silverwind that's trivial: you iterate over `arr` from `index + 1` up to `arr.length - 1` and check if the `value` is equal to `arr[i]`. If it is - you return `false`, otherwise you return `true` in the end of the function. – zerkms Apr 27 '15 at 11:32
  • @zerkms not sure about `index + 1`, because that would be out of bounds for the last element. Here's what I have right now: http://jsfiddle.net/sygrp9u0/1/ – silverwind Apr 27 '15 at 11:46
  • @silverwind it will not be: `for (var i = index + 1; i < arr.length - 1; ++i) {}` will never get out of bounds http://jsfiddle.net/sygrp9u0/4/ – zerkms Apr 27 '15 at 11:50
  • @zerkms Thanks, that looks to work flawlessly, even with different sorting, I'd accept that comment as an answer if I could :) – silverwind Apr 27 '15 at 11:58
  • @silverwind it's still a duplicate :-) The application is not that relevant – zerkms Apr 27 '15 at 12:00
  • @zerkms I don't agree with that duplicate, as there the other answer only points to a method that can be used to solve this, there's probably a few clever ways to do this besides a deep-equal. I also noticed your solution doesn't filter correctly in some cases, but I'll just write something up tailored to my use case now. – silverwind Apr 27 '15 at 15:31
  • And here's my solution: https://jsfiddle.net/sygrp9u0/7/ – silverwind Apr 27 '15 at 16:30
  • 1
    @silverwind "doesn't filter correctly in some cases" --- in what "other" cases? – zerkms Apr 27 '15 at 19:39
  • @zerkms: Here's your last answer, updated with my shuffled array: http://jsfiddle.net/sygrp9u0/8/ - Note it contains `"a": "foo", "c": "baz", "b": "bar"` twice. – silverwind Apr 27 '15 at 21:23
  • @silverwind it's a trivial off-by-one mistake: http://jsfiddle.net/sygrp9u0/9/ – zerkms Apr 27 '15 at 21:35

3 Answers3

0

If your objects all have the same keys, you can use lodash's or underscore's _.findWhere function:

Performs a deep comparison between each element in collection and the source object, returning the first element that has equivalent property values.

var arr = [
  {a: "foo", b: "bar", c: "baz" },
  {a: "foo", b: "bar", c: "qux" },
  {a: "foo", b: "bar", c: "baz" },
  {a: "foo", b: "bar", c: "qux" },
  {a: "bar", b: "foo", c: "qux" },
  {a: "bar", b: "qux", c: "foo" },
  {a: "bar", b: "foo", c: "qux" }
];

function uniqueObjects(arr) {
  var u = [];
  arr.forEach(function(obj) {
    if (!_.findWhere(u, obj)) {
      u.push(obj);
    }
  });
  return u
}

document.getElementById('result').innerHTML = JSON.stringify(uniqueObjects(arr),null,2);
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.7.0/lodash.min.js"></script>
<pre id='result'></pre>

If not, a deep comparison can be made by using _.matches both ways:

var arr = [
  {a: "foo", b: "bar", c: "baz" },
  {a: "foo", b: "bar", c: "qux" },
  {a: "foo", b: "bar", c: "baz" },
  {a: "foo", b: "bar", c: "qux" },
  {a: "bar", b: "foo", c: "qux" },
  {a: "bar", b: "qux", c: "foo" },
  {a: "bar", b: "foo", c: "qux" },
  // Missing property
  {a: "foo", b: "bar" },
  // Ordering
  {a: "foo", c: "qux" },
  {c: "qux", a: "foo" }
];

function uniqueObjects(arr) {
  var u = [];
  arr.forEach(function(obj) {
    if (!u.filter(deepCompare(obj)).length) {
      u.push(obj);
    }
  });
  return u;
}

function deepCompare(obj) {
  return function(source) {
    return _.matches(obj)(source) && _.matches(source)(obj);
  };
}

document.getElementById('result').innerHTML = JSON.stringify(uniqueObjects(arr),null,2);
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.7.0/lodash.min.js"></script>
<pre id='result'></pre>
dting
  • 38,604
  • 10
  • 95
  • 114
  • `_.findWhere` does not guarantee the elements equality, it just checks the provided attributes. Eg: `{a: 1}` would be found as the `{a: 1, b: 2}` object – zerkms Apr 27 '15 at 11:14
  • Thanks, bad assumption that all the objects would have all the same keys. Updated my answer. – dting Apr 27 '15 at 12:46
-2

I have used the approach i have mentioned in my comment.

var arr = [
  {c: "baz", a: "foo", b: "bar" },
  {c: "qux", a: "foo", b: "bar" },
  {c: "baz", b: "bar", a: "foo" },
  {c: "qux", b: "bar", a: "foo" },
  {a: "bar", c: "qux", b: "foo" },
  {a: "bar", c: "foo", b: "qux" },
  {a: "bar", b: "foo", c: "qux" }
];

var modified = arr.map(function (value) {
    var result = [];
    var keys = Object.keys(value).sort();

    for (var i = 0; i < keys.length; i++) {
        result.push(value[keys[i]]);
    }
    return result.join('');
});

for (var i = (modified.length - 1); i >= 0; i--) {
    if (modified.indexOf(modified[i]) !== i) {
        arr.splice(i, 1);
    }
}


alert(JSON.stringify(arr));
Vigneswaran Marimuthu
  • 2,452
  • 13
  • 16
  • What if some values already have `,` as a part? The solution is prone to give false positive matches. – zerkms Apr 27 '15 at 10:55
  • @zerkms Update the code not to use any delimiter – Vigneswaran Marimuthu Apr 27 '15 at 11:05
  • "Update the code not to use any delimiter" --- what does it mean? Your code may give false positives. – zerkms Apr 27 '15 at 11:10
  • This looks to work for my case, but I can see it failing in cases where the properties are ordered differently. – silverwind Apr 27 '15 at 11:14
  • @silverwind I rearranged the properties. Still i am seeing the same result. [Fiddle](https://jsfiddle.net/s4fqeshf/). Can you update the fiddle with failing scenario ? – Vigneswaran Marimuthu Apr 27 '15 at 11:18
  • @VigneswaranMarimuthu I edited the source array to mix up the property ordering. – silverwind Apr 27 '15 at 11:22
  • @VigneswaranMarimuthu https://jsfiddle.net/s4fqeshf/1/ – zerkms Apr 27 '15 at 11:23
  • @VigneswaranMarimuthu Still won't work, see https://jsfiddle.net/s4fqeshf/2/ – silverwind Apr 27 '15 at 11:33
  • @silverwind Thanks for pointing :) I have update the code. As per Zerkms, i will look at it. Thanks Zerkms for pointing – Vigneswaran Marimuthu Apr 27 '15 at 11:53
  • @zerkms If we use the delimiter as `new Date().getTime()`, what is the chance of false positive results ? I hope it is less but i can agree, it is a string comparison overhead. – Vigneswaran Marimuthu Apr 27 '15 at 11:59
  • 1
    @VigneswaranMarimuthu please see the [comments](http://stackoverflow.com/questions/29893291/filter-array-of-objects-for-unique-elements/29893756?noredirect=1#comment47912554_29893291) to the question for a **proper solution**.. The "chance" is a terrible way to develop, it's programming not lottery. The solution either works, or not. – zerkms Apr 27 '15 at 12:01
-3

Use lodash! https://lodash.com/docs#uniq

Creates a duplicate-free version of an array, using SameValueZero for equality comparisons, in which only the first occurence of each element is kept. Providing true for isSorted performs a faster search algorithm for sorted arrays. If an iteratee function is provided it is invoked for each element in the array to generate the criterion by which uniqueness is computed. The iteratee is bound to thisArg and invoked with three arguments: (value, index, array).

Include it in your HTML <script src="bower_components/lodash/lodash.js"></script>

Then in your controller.

var filteredArray = _.uniq(arr, function(item, key, a) { 
    return item.a;
});

http://jsfiddle.net/8xp3o7b3/3/

Ed Knowles
  • 1,925
  • 1
  • 16
  • 24