Here's a JavaScript TupleSet, i.e. a set that contains unique tuples where order within each tuple does not matter (similar in some ways to MultiKeyMap in Java):
export class TupleSet {
tuples = new Set<Array<unknown>>()
add(tuple: Array<unknown>) {
return this.#findTuple(
tuple,
() => false, // found (not added)
() => {
this.tuples.add(tuple)
return true // not found (added)
},
)
}
delete(tuple: Array<unknown>) {
return this.#findTuple(
tuple,
tuple => {
this.tuples.delete(tuple)
return true // found (deleted)
},
() => false, // not found (not deleted)
)
}
has(tuple: Array<unknown>) {
return this.#findTuple(
tuple,
() => true,
() => false,
)
}
#findTuple(tuple: Array<unknown>, onFound: (foundTuple: Array<unknown>) => boolean, onNotFound: () => boolean) {
outer: for (const testTuple of this.tuples) {
if (tuple.length !== testTuple.length) continue
for (const value of tuple) if (!testTuple.includes(value)) continue outer
return onFound(testTuple)
}
return onNotFound()
}
}
How can we make a weak TupleSet (named WeakTupleSet) such that if at least one item in a tuple is no longer referenced outside of the WeakTupleSet, the tuple is automatically removed from the WeakTupleSet?
(If the user code outside of the set does not have all the original references to the items in a tuple, it is impossible to use that tuple again, hence the tuple should be removed from the set)
Example:
const set = new WeakTupleSet()
globalThis.obj1 = {}
globalThis.obj2 = {}
globalThis.obj3 = {}
const tuple = [globalThis.obj1, globalThis.obj2, globalThis.obj3]
set.add(tuple)
set.has(tuple) // true
// later, unreference obj2
tuple[1] = undefined
delete globalThis.obj2
// even later, the set should no longer contain the tuple, and obj2 is fully garbage collected
I have a feeling a WeakRef dance may be possible.
I don't think you'll need
WeakRefs, but aWeakSetwould come in handy and with a known size it could be used for comparisons. Then use aFinalizationRegistryas you planned to remove incomplete entries from your set.The key is the
#cleanup.register(value, newEntry, newEntry)inadd: when any of thevaluesgets garbage-collected, thenewEntrygets removed from the#entries. It also unregisters the cleanup for the other values to prevent theFinalizationRegistryitself from holding onto theWeakEntryunnecessarily (until all its elements get garbage-collected, which may be never).You can do the same with arbitrary tuples (arrays) instead of
Sets, but you say that order within your "tuples" would not matter so they're sets. Make eachWeakEntryan array containingWeakRefs then.