From db383f251eb530cb7eaecf82c25bad9b82b682cf Mon Sep 17 00:00:00 2001 From: Marek Rusinowski Date: Sun, 5 Jan 2025 01:01:49 +0100 Subject: [PATCH] Add MultiIndex data structure --- src/multiIndex.test.ts | 47 +++++++++++++++++++ src/multiIndex.ts | 100 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 src/multiIndex.test.ts create mode 100644 src/multiIndex.ts diff --git a/src/multiIndex.test.ts b/src/multiIndex.test.ts new file mode 100644 index 0000000..336abcd --- /dev/null +++ b/src/multiIndex.test.ts @@ -0,0 +1,47 @@ +import { test, suite } from 'node:test'; +import assert from 'node:assert/strict'; +import { MultiIndex } from './multiIndex.js'; + +suite('MultiIndex', () => { + test('set', () => { + const mi = new MultiIndex({ a1: '', a2: 0, a3: '' }); + assert.equal(mi.set({ a1: 'a1.1', a2: 1, a3: 'a3.1' }), true); + assert.equal(mi.set({ a1: 'a1.2', a2: 2, a3: 'a3.2' }), true); + assert.equal(mi.set({ a1: 'a1.2', a2: 2, a3: 'a3.2' }), false); + assert.throws(() => { + mi.set({ a1: 'a1.2', a2: 2, a3: 'a3.2_prime' }); + }); + assert.equal(mi.size, 2); + }); + + test('get', () => { + const mi = new MultiIndex({ a1: '', a2: 0, a3: '' }); + mi.set({ a1: 'a1.1', a2: 1, a3: 'a3.1' }); + + assert.equal(mi.get('a1', 'a1.1')?.a3, 'a3.1'); + assert.equal(mi.get('a2', 1)?.a1, 'a1.1'); + assert.equal(mi.get('a3', 'a3.1')?.a1, 'a1.1'); + + assert.equal(mi.get('a1', 'non-existent'), undefined); + }); + + test('has', () => { + const mi = new MultiIndex({ a1: '', a2: 0 }); + mi.set({ a1: 'a1.1', a2: 1 }); + + assert.equal(mi.has('a1', 'a1.1'), true); + assert.equal(mi.has('a1', 'non-existent'), false); + }); + + test('delete', () => { + const mi = new MultiIndex({ a1: '', a2: 0 }); + mi.set({ a1: 'a1.1', a2: 1 }); + mi.set({ a1: 'a1.2', a2: 2 }); + assert.equal(mi.size, 2); + + assert.equal(mi.delete('a1', 'a1.1'), true); + assert.ok(!mi.hasAny({ a1: 'a1.1', a2: 1 })); + assert.ok(mi.hasAll({ a1: 'a1.2', a2: 2 })); + assert.equal(mi.size, 1); + }); +}); diff --git a/src/multiIndex.ts b/src/multiIndex.ts new file mode 100644 index 0000000..dea934c --- /dev/null +++ b/src/multiIndex.ts @@ -0,0 +1,100 @@ +/** + * MultiIndex is a n-way mapping between values. In mathematical sense, + * it's a bijection between n-sets of values. + * + * Each of the sets has a name and type: modeled by the template type argument: + * + * { + * 'set_name1': type + * 'set_name2': some_type2 + * } + * + * The multi-index supports adding new elements and looking up values from all + * the sets given the value from only one of them. + */ +export class MultiIndex { + private m: { [key in keyof K]: Map }; + + /** + * Constructs a new MultiIndex. + * + * @param idx Any index key. It's not added to the multi-index, just needed + * because of the type erasure. + */ + constructor(private idx: K) { + this.m = {} as typeof this.m; + for (const k in idx) { + this.m[k] = new Map(); + } + } + + /** + * Tests if any of the values from the index key are already in the set. + */ + hasAny(idx: K): boolean { + for (const k in idx) { + if (this.m[k].has(idx[k])) { + return true; + } + } + return false; + } + + /** + * Tests if the whole index key is already in the set. + */ + hasAll(idx: K): boolean { + for (const k in this.m) { + const v = this.m[k].get(idx[k]); + if (v) { + for (const k in idx) { + if (v[k] != idx[k]) { + return false; + } + } + return true; + } + break; + } + return false; + } + + set(idx: K): boolean { + if (this.hasAll(idx)) { + return false; + } + if (this.hasAny(idx)) { + throw new Error('Trying to set incompatible index key'); + } + for (const k in idx) { + this.m[k].set(idx[k], idx); + } + return true; + } + + get(set: U, key: K[U]): K | undefined { + return this.m[set].get(key); + } + + has(set: U, key: K[U]): boolean { + return this.m[set].has(key); + } + + delete(set: U, key: K[U]): boolean { + const k = this.m[set].get(key); + if (!k) { + return false; + } + for (const i in k) { + this.m[i].delete(k[i]); + } + return true; + } + + get size(): number { + for (const k in this.m) { + return this.m[k].size; + } + return 0; + } +}