How to Customize Object Equality for JavaScript Set

How to customize object equality for JavaScript Set

Update 3/2022

There is currently a proposal to add Records and Tuples (basically immutable Objects and Arrays) to Javascript. In that proposal, it offers direct comparison of Records and Tuples using === or !== where it compares values, not just object references AND relevant to this answer both Set and Map objects would use the value of the Record or Tuple in key comparisons/lookups which would solve what is being asked for here.

Since the Records and Tuples are immutable (can't be modified) and because they are easily compared by value (by their contents, not just their object reference), it allows Maps and Sets to use object contents as keys and the proposed spec explicitly names this feature for Sets and Maps.

This original question asked for customizability of a Set comparison in order to support deep object comparison. This doesn't propose customizability of the Set comparison, but it directly supports deep object comparison if you use the new Record or a Tuple instead of an Object or an Array and thus would solve the original problem here.

Note, this proposal advanced to Stage 2 in mid-2021. It has been moving forward recently, but is certainly not done.

Mozilla work on this new proposal can be tracked here.


Original Answer

The ES6 Set object does not have any compare methods or custom compare extensibility.

The .has(), .add() and .delete() methods work only off it being the same actual object or same value for a primitive and don't have a means to plug into or replace just that logic.

You could presumably derive your own object from a Set and replace .has(), .add() and .delete() methods with something that did a deep object comparison first to find if the item is already in the Set, but the performance would likely not be good since the underlying Set object would not be helping at all. You'd probably have to just do a brute force iteration through all existing objects to find a match using your own custom compare before calling the original .add().

Here's some info from this article and discussion of ES6 features:

5.2 Why can’t I configure how maps and sets compare keys and values?

Question: It would be nice if there were a way to configure what map
keys and what set elements are considered equal. Why isn’t there?

Answer: That feature has been postponed, as it is difficult to
implement properly and efficiently. One option is to hand callbacks to
collections that specify equality.

Another option, available in Java, is to specify equality via a method
that object implement (equals() in Java). However, this approach is
problematic for mutable objects: In general, if an object changes, its
“location” inside a collection has to change, as well. But that’s not
what happens in Java. JavaScript will probably go the safer route of
only enabling comparison by value for special immutable objects
(so-called value objects). Comparison by value means that two values
are considered equal if their contents are equal. Primitive values are
compared by value in JavaScript.

user defined object equality for a set in harmony (es6)

Is there a means to define equality for my objects to solve my problem?

No not really. There has been some discussion about this on the mailing list. The result is:

  • Build your own Set/Map abstraction on top of Set/Map, which would convert the objects to a primitive value according to your hashing function.
  • Wait for value objects coming in ES7.

How to determine equality for two JavaScript objects?

The short answer

The simple answer is: No, there is no generic means to determine that an object is equal to another in the sense you mean. The exception is when you are strictly thinking of an object being typeless.

The long answer

The concept is that of an Equals method that compares two different instances of an object to indicate whether they are equal at a value level. However, it is up to the specific type to define how an Equals method should be implemented. An iterative comparison of attributes that have primitive values may not be enough: an object may contain attributes which are not relevant to equality. For example,

 function MyClass(a, b)
{
var c;
this.getCLazy = function() {
if (c === undefined) c = a * b // imagine * is really expensive
return c;
}
}

In this above case, c is not really important to determine whether any two instances of MyClass are equal, only a and b are important. In some cases c might vary between instances and yet not be significant during comparison.

Note this issue applies when members may themselves also be instances of a type and these each would all be required to have a means of determining equality.

Further complicating things is that in JavaScript the distinction between data and method is blurred.

An object may reference a method that is to be called as an event handler, and this would likely not be considered part of its 'value state'. Whereas another object may well be assigned a function that performs an important calculation and thereby makes this instance different from others simply because it references a different function.

What about an object that has one of its existing prototype methods overridden by another function? Could it still be considered equal to another instance that it otherwise identical? That question can only be answered in each specific case for each type.

As stated earlier, the exception would be a strictly typeless object. In which case the only sensible choice is an iterative and recursive comparison of each member. Even then one has to ask what is the 'value' of a function?

comparing ECMA6 sets for equality

Try this:

const eqSet = (xs, ys) =>
xs.size === ys.size &&
[...xs].every((x) => ys.has(x));

const ws = new Set([1, 2, 3]);
const xs = new Set([1, 3, 2]);
const ys = new Set([1, 2, 4]);
const zs = new Set([1, 2, 3, 4]);

console.log(eqSet(ws, xs)); // true
console.log(eqSet(ws, ys)); // false
console.log(eqSet(ws, zs)); // false

How to create a Set of user-defined objects in Javascript with a user-defined equality function?

If you would accept to work with/override valueOf, then you could proceed like this:

// Implementation of special Set:function mySet() {    var self = this;    self.size = 0;    // Use a private map that will be keyed by the valueOf() of each added item:     var map = new Map();    self.add = function (item) {        map.set(item.valueOf(), item);        self.size = map.size;    };    self.has = function (item) {        return map.has(item.valueOf());    };    self[Symbol.iterator] = function* () {        for (var pair of map) {            yield pair[1]; // return the item ( not the valueOf() in [0])        }    };    // etc...}
// Test code:myClass = function(val){ var self = this; self.val = val; self.valueOf = function () { return self.val; }; }
a = new myClass(1.0);b = new myClass(2.0);c = new myClass(2.0);
s = new mySet(); //does not have to be actual "Set"s.add(a);s.add(b);s.add(c);
document.write('size: ' + s.size + '<br>');document.write('has(a): ' + s.has(a) + '<br>');document.write('has(b): ' + s.has(b) + '<br>');document.write('has(c): ' + s.has(c) + '<br>');
for (item of s) { document.write('stored item: ' + JSON.stringify(item) + '<br>');};


Related Topics



Leave a reply



Submit