3

I am very new to Javascript and wondering if it is possible to create a Set of user-defined objects in a way that allows me to specify the function that is used to make equality comparisons?

Here's a contrived example just to illustrate the functionality that I'm looking for:

myClass = function(val){
                 var self = this;
                 self.val = val
                 self.equals = //used defined equals function 
           }

a = new myClass(1.0)
b = new myClass(2.0)
c = new myClass(2.0)

s = new Set() //does not have to be actual "Set"
s.add(a)
s.add(b)
s.add(c)

s.size === 2 //returns true
s.has(a) //returns true
s.has(b) //returns true
s.has(c) //returns true

I've found Set implementations (like this one), but it seems like are only designed for values, not user-defined objects. I suspect that there are other implementations that use === but this would not be useful in my case since I don't believe that I can override === either.

My question is very similar to this question. I'm posting it again since: a) I don't necessarily need a native ES6 solution and would be open to using a third party library. and b) it's been some time since that question was posted.

Community
  • 1
  • 1
Berk U.
  • 7,018
  • 6
  • 44
  • 69
  • If `equals` is the only way to verify equality, an implementation will never be efficient, as adding new members to the set will require the implementation to go through all set members and call `equals` to compare the new item with each of them. Would instead the use of the `valueOf` method be an option? – trincot Apr 11 '16 at 21:26
  • @trincot That makes sense. I'm still very new to JS, so I'm not familiar with `valueOf` (is that just the return value of `myClass`?). Either way, `valueOf` would definitely be an option if I can define as I wish for any of the classes that I create. – Berk U. Apr 11 '16 at 21:33
  • 1
    OK, I will prepare an answer along those lines, then you can decide whether it suits your needs. – trincot Apr 11 '16 at 21:34

1 Answers1

2

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>');
};

Edit

Months later, I answered a similar question, where I did not suggest the use of valueOf, but a function that could be provided to the MySet constructor, defaulting to JSON.stringify.

Community
  • 1
  • 1
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thank you! Two follow-up questions: 1) Since this [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) implementation uses valueOf, would that also work with `myClass`? 2) Would this [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) implementation work? – Berk U. Apr 11 '16 at 22:01
  • You're welcome. Not sure what you are asking. If you mean to say that the ES6 Set implementation uses valueOf: that is not the case. At least it does not work like that when I try (size is 3 in the test case). The same goes for the ES6 Map implementation. – trincot Apr 11 '16 at 22:07
  • Yes sorry that was what I meant. I'm just trying to find a Map implementation that let's me use the solution you came up with. Do you know of a Map implementation that is keyed by the valueOf() each added item? – Berk U. Apr 11 '16 at 22:11
  • 1
    A map stores key/value pairs. If you want the key to be the valueOf of the value, then that is exactly what I did in `mySet`, except that you don't explicitly provide the `valueOf()` result to it, as it will do that by itself. But internally the map does get this key/value pair: `item.valueOf()/item`. But maybe I misunderstood you? – trincot Apr 11 '16 at 22:16
  • Or, if you mean you want actually something like this solution, but completely worked out with all the methods and properties you can expect: no I don't know of such implementation, but you could add to this what you need as you go. I just added the iterator, so that you can write `for ... of` loops on such sets. – trincot Apr 11 '16 at 22:30
  • Ohhh nevermind! I thought that you had to load an external library to be able to call `var map = new Map()`. – Berk U. Apr 11 '16 at 22:56
  • FYI: [I answered a similar question](http://stackoverflow.com/a/38581211/5459839) just now, where I did not suggest the use of *valueOf*, but a function that could be provided to the *MySet* constructor, defaulting to `JSON.stringify`. – trincot Jul 26 '16 at 05:20