3

What is the best way to implement a Set of coordinates in JavaScript? I would like to be able to do things like:

let s=new Set();
s.add([1,1]);
if (s.has([1,1])) // false, since these are different arrays

The above doesn't work, since the Set is storing a reference to the array instead of the contents.

DavidXYZ
  • 106
  • 9
  • 1
    The default `.has()` on a Set object looks for the physical same object, not any old object with the same properties. So, you can't directly use the existing `.has()` for this. It looks like hev's answer is probably your best bet. – jfriend00 Jul 30 '20 at 22:08

4 Answers4

3

You can subclass Set for more flexibility.

class ObjectSet extends Set{
  add(elem){
    return super.add(typeof elem === 'object' ? JSON.stringify(elem) : elem);
  }
  has(elem){
    return super.has(typeof elem === 'object' ? JSON.stringify(elem) : elem);
  }
}
let s=new ObjectSet();
s.add([1,1]);
console.log(s.has([1,1]))
console.log(s.has([1,2,3]));
console.log([...s]);
console.log([...s].map(JSON.parse));//get objects back
iota
  • 34,586
  • 7
  • 32
  • 51
1

If we can assume that our tuples are finite integers, they could be encoded as a float.

class TupleSet extends Set{
    add(elem){
      return super.add((typeof elem === 'object' 
                        && Number.isSafeInteger(elem[0]) 
                        && Number.isSafeInteger(elem[1])) 
                        ? elem[0]+elem[1]/10000000 : elem);
    }
    has(elem){
      return super.has((typeof elem === 'object'
                        && Number.isSafeInteger(elem[0]) 
                        && Number.isSafeInteger(elem[1])) 
                        ? elem[0]+elem[1]/10000000 : elem);
    }
  }
  function TupleSetParse(elem){
     return (Number.isFinite(elem)?
        [Math.round(elem),Math.round((elem-Math.round(elem))*10000000)]:elem);
  }
  
  let s=new TupleSet();
  s.add([1,5]);
  s.add([1000000,1000000]);
  s.add([-1000000,-1000000]);
  console.log(s.has([1,5]));  // true
  console.log(s.has([1,2]));  // false
  console.log([...s].map(TupleSetParse));  
    // [ [ 1, 5 ], [ 1000000, 1000000 ], [ -1000000, -1000000 ] ]

Of course, this is limited in range. And it is fragile to some malformed input, so additional error checking should be added. However, after some testing, this method is only 25% better in speed and memory usage than the JSON.stringify approach. So, JSON is the preferred approach.

DavidXYZ
  • 106
  • 9
  • An alternative (with the same `isSafeInteger` caveat) is to use a pairing function, e.g.: https://math.stackexchange.com/questions/23503/create-unique-number-from-2-numbers and https://stackoverflow.com/questions/919612/mapping-two-integers-to-one-in-a-unique-and-deterministic-way – vasilyrud May 09 '21 at 15:51
0

This can be done with strings:

let s=new Set();
s.add("1,1");
s.add("2,2");
console.log(s.has("1,1"), s.has("1,2")); // true false

However, I would prefer to do this with some type of numeric tuple to avoid repeated string conversion logic.

DavidXYZ
  • 106
  • 9
0

If you only plan to store pairs of coords, another possibility is to use a combination of a Map (for the first coord) and a Set (for the second coord).

function TupleSet() {
    this.data = new Map();

    this.add = function([first, second]) {
        if (!this.data.has(first)) {
            this.data.set(first, new Set());
        }

        this.data.get(first).add(second);
        return this;
    };

    this.has = function([first, second]) {
        return (
            this.data.has(first) &&
            this.data.get(first).has(second)
        );
    };

    this.delete = function([first, second]) {
        if (!this.data.has(first) ||
            !this.data.get(first).has(second)
        ) return false;

        this.data.get(first).delete(second);
        if (this.data.get(first).size === 0) {
            this.data.delete(first);
        }

        return true;
    };
}

let mySet = new TupleSet();
mySet.add([0,2]);
mySet.add([1,2]);
mySet.add([0,3]);
console.log(mySet.has([1,3]));
console.log(mySet.has([0,2]));
mySet.delete([0,2]);
console.log(mySet.has([0,2]));

Unfortunately, unlike a normal Set for which:

You can iterate through the elements of a set in insertion order. — MDN Set

This approach will, for the example above, iterate in the order:

[0,2]
[0,3]
[1,2]
vasilyrud
  • 688
  • 2
  • 8
  • 13