When facing the issue of tuple map keys, if any of the keys of the tuple is non-Scalar, then the issue should be handled using nested Maps, see the following example with a tuple of two values:
// An animal care enterprise has a fleet of vehicles and has decided that for
// each animal and each vehicle, the right for the animal to enter the vehicle
// would be either:
// - to be defined
// - authorized
// - unauthorized
// This situation corresponds to a map from tuples of (Animal, Vehicle) to
// boolean. It can be handled in JS/TS using nested Map instances:
const animalA = new Animal();
const vehicleA = new Vehicle();
// instead of Map<[Animcal, Vehicle], boolean>, we have:
const animalCanEnterVehicle: Map<Animal, Map<Vehicle, boolean>> = new Map();
// Values:
// - true: authorized
// - false: unauthorized
// - undefined: to be defined
// Reading
const authorized = animalCanEnterVehicle.get(animalA)?.get(vehicleA);
// Writing
const vehicleMapForAnimal = animalCanEnterVehicle.get(animalA) ?? new Map();
vehicleMapForAnimal.set(vehicle, authorized);
animalCanEnterVehicle.set(animalA, vehicleMapForAnimal);
// Removing
const vehicleMapForAnimal = animalCanEnterVehicle.get(animalA);
if (vehicleMapForAnimal) {
vehicleMapForAnimal.remove(vehicleA);
if (vehicleMapForAnimal.size === 0) {
animalCanEnterVehicle.delete(animalA);
}
}
It is possible to create a custom class which manages mapping pairs of values for keys:
export class PairMap<TKA, TKB, TV> {
root: Map<TKA, Map<TKB, TV>>
constructor() {
this.root = new Map()
}
get size() {
let total = 0
for (const map of this.root.values()) {
total += map.size
}
return total
}
has(keyA: TKA, keyB: TKB): boolean {
return this.root.get(keyA)?.has(keyB) ?? false
}
get(keyA: TKA, keyB: TKB): TV {
return this.root.get(keyA)?.get(keyB)
}
set(keyA: TKA, keyB: TKB, value: TV) {
const map = this.root.get(keyA) ?? new Map()
map.set(keyB, value)
this.root.set(keyA, map)
}
delete(keyA: TKA, keyB: TKB): boolean {
const map = this.root.get(keyA)
let result = false
if (map) {
result = map.delete(keyB)
if (map.size === 0) {
this.root.delete(keyA)
}
}
return result
}
clear() {
this.root.clear()
}
keys() {
return [...this.root.entries()].flatMap(([keyA, map]) =>
[...map.keys()].map((keyB) => [keyA, keyB]),
)
}
values() {
return [...this.root.values()].flatMap((map) => [...map.values()])
}
entries() {
const me = this
function* entries() {
for (const [keyA, map] of me.root.entries()) {
yield* [...map.entries()].map(([keyB, value]) => [keyA, keyB, value])
}
}
return entries()
}
forEach(callback: (value: TV, keyA: TKA, keyB: TKB) => void) {
for (const [keyA, map] of this.root.entries()) {
for (const [keyB, value] of map.entries()) {
callback(value, keyA, keyB)
}
}
}
}
Note: all the methods of the PairMap class have been tested using a harness I wrote, see here