From db2957c64085819dad32a8bad16aa248872bd0bb Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 20 Jul 2024 14:04:59 +0200 Subject: [PATCH] refactor(associative): migrate/remove sparse set features (#486) BREAKING CHANGE: migrate sparse set features to thi.ng/sparse-set pkg - remove obsolete files - update pkg --- packages/associative/package.json | 11 +- packages/associative/src/index.ts | 1 - packages/associative/src/sparse-set.ts | 264 -------------------- packages/associative/test/sparseset.test.ts | 80 ------ 4 files changed, 2 insertions(+), 354 deletions(-) delete mode 100644 packages/associative/src/sparse-set.ts delete mode 100644 packages/associative/test/sparseset.test.ts diff --git a/packages/associative/package.json b/packages/associative/package.json index 53aed3cbe6..ae0d965a52 100644 --- a/packages/associative/package.json +++ b/packages/associative/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/associative", "version": "6.3.65", - "description": "Alternative Map and Set implementations with customizable equality semantics & supporting operations, plain object utilities", + "description": "ES Map/Set-compatible implementations with customizable equality semantics & supporting operations", "type": "module", "module": "./index.js", "typings": "./index.d.ts", @@ -42,7 +42,6 @@ "@thi.ng/checks": "^3.6.8", "@thi.ng/dcons": "^3.2.118", "@thi.ng/equiv": "^2.1.62", - "@thi.ng/errors": "^2.5.12", "@thi.ng/object-utils": "^1.0.0", "@thi.ng/transducers": "^9.0.10", "tslib": "^2.6.3" @@ -63,13 +62,10 @@ "map", "object", "set", - "skiplist", - "sort", "sparse", "trie", "typescript", - "union", - "value-semantics" + "union" ], "publishConfig": { "access": "public" @@ -134,9 +130,6 @@ "./ll-set": { "default": "./ll-set.js" }, - "./sparse-set": { - "default": "./sparse-set.js" - }, "./union": { "default": "./union.js" } diff --git a/packages/associative/src/index.ts b/packages/associative/src/index.ts index a6ff811a9f..751b164c6c 100644 --- a/packages/associative/src/index.ts +++ b/packages/associative/src/index.ts @@ -11,5 +11,4 @@ export * from "./intersection.js"; export * from "./into.js"; export * from "./join.js"; export * from "./ll-set.js"; -export * from "./sparse-set.js"; export * from "./union.js"; diff --git a/packages/associative/src/sparse-set.ts b/packages/associative/src/sparse-set.ts deleted file mode 100644 index 99125440fb..0000000000 --- a/packages/associative/src/sparse-set.ts +++ /dev/null @@ -1,264 +0,0 @@ -import type { Fn3, IEquiv, Pair, UIntArray } from "@thi.ng/api"; -import { isNumber } from "@thi.ng/checks/is-number"; -import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; -import type { IEquivSet } from "./api.js"; -import { dissoc } from "./dissoc.js"; -import { __inspectable } from "./internal/inspect.js"; -import { into } from "./into.js"; - -interface SparseSetProps { - dense: UIntArray; - sparse: UIntArray; - n: number; -} - -/** @internal */ -const __private = new WeakMap, SparseSetProps>(); - -/** @internal */ -const __fail = () => illegalArgs(`dense & sparse arrays must be of same size`); - -/** - * After "An Efficient Representation for Sparse Sets" Preston Briggs and Linda - * Torczon (1993) - * - * - https://research.swtch.com/sparse - * - https://programmingpraxis.com/2012/03/09/sparse-sets/ - * - https://blog.molecular-matters.com/2013/07/24/adventures-in-data-oriented-design-part-3c-external-references/ - */ -@__inspectable -export abstract class ASparseSet - extends Set - implements IEquiv -{ - protected constructor(dense: T, sparse: T) { - super(); - __private.set(this, { dense, sparse, n: 0 }); - } - - [Symbol.iterator]() { - return this.keys(); - } - - get size(): number { - return __private.get(this)!.n; - } - - get capacity(): number { - return __private.get(this)!.dense.length; - } - - clear() { - __private.get(this)!.n = 0; - } - - equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set) || this.size !== o.size) { - return false; - } - const $this = __private.get(this)!; - const d = $this.dense; - for (let i = $this.n; i-- > 0; ) { - if (!o.has(d[i])) { - return false; - } - } - return true; - } - - add(key: number) { - const $this = __private.get(this)!; - const { dense, sparse, n } = $this; - const max = dense.length; - const i = sparse[key]; - if (key < max && n < max && !(i < n && dense[i] === key)) { - dense[n] = key; - sparse[key] = n; - $this.n++; - } - return this; - } - - delete(key: number) { - const $this = __private.get(this)!; - const { dense, sparse } = $this; - const i = sparse[key]; - if (i < $this.n && dense[i] === key) { - const j = dense[--$this.n]; - dense[i] = j; - sparse[j] = i; - return true; - } - return false; - } - - has(key: number): boolean { - const $this = __private.get(this)!; - const i = $this.sparse[key]; - return i < $this.n && $this.dense[i] === key; - } - - get(key: number, notFound = -1) { - return this.has(key) ? key : notFound; - } - - first() { - const $this = __private.get(this)!; - return $this.n ? $this.dense[0] : undefined; - } - - into(keys: Iterable) { - return into(this, keys); - } - - disj(keys: Iterable) { - return dissoc(this, keys); - } - - forEach(fn: Fn3, void>, thisArg?: any) { - const $this = __private.get(this)!; - const d = $this.dense; - const n = $this.n; - for (let i = 0; i < n; i++) { - const v = d[i]; - fn.call(thisArg, v, v, this); - } - } - - *entries(): IterableIterator> { - const { dense, n } = __private.get(this)!; - for (let i = 0; i < n; i++) { - yield [dense[i], dense[i]]; - } - } - - *keys(): IterableIterator { - const { dense, n } = __private.get(this)!; - for (let i = 0; i < n; i++) { - yield dense[i]; - } - } - - values() { - return this.keys(); - } - - protected __copyTo>(dest: S) { - const $this = __private.get(this)!; - const $c = __private.get(dest)!; - $c.dense = $this.dense.slice(); - $c.sparse = $this.sparse.slice(); - $c.n = $this.n; - return dest; - } -} - -export class SparseSet8 - extends ASparseSet - implements IEquivSet -{ - constructor(dense: Uint8Array, sparse: Uint8Array); - constructor(n: number); - constructor(n: number | Uint8Array, sparse?: Uint8Array) { - isNumber(n) - ? super(new Uint8Array(n), new Uint8Array(n)) - : n.length === sparse!.length - ? super(n, sparse!) - : __fail(); - } - - get [Symbol.species]() { - return SparseSet8; - } - - get [Symbol.toStringTag]() { - return "SparseSet8"; - } - - copy() { - return this.__copyTo(new SparseSet8(0)); - } - - empty() { - return new SparseSet8(this.capacity); - } -} - -export class SparseSet16 - extends ASparseSet - implements IEquivSet -{ - constructor(dense: Uint16Array, sparse: Uint16Array); - constructor(n: number); - constructor(n: number | Uint16Array, sparse?: Uint16Array) { - isNumber(n) - ? super(new Uint16Array(n), new Uint16Array(n)) - : n.length === sparse!.length - ? super(n, sparse!) - : __fail(); - } - - get [Symbol.species]() { - return SparseSet16; - } - - get [Symbol.toStringTag]() { - return "SparseSet16"; - } - - copy() { - return this.__copyTo(new SparseSet16(0)); - } - - empty() { - return new SparseSet16(this.capacity); - } -} - -export class SparseSet32 - extends ASparseSet - implements IEquivSet -{ - constructor(dense: Uint32Array, sparse: Uint32Array); - constructor(n: number); - constructor(n: number | Uint32Array, sparse?: Uint32Array) { - isNumber(n) - ? super(new Uint32Array(n), new Uint32Array(n)) - : n.length === sparse!.length - ? super(n, sparse!) - : __fail(); - } - - get [Symbol.species]() { - return SparseSet32; - } - - get [Symbol.toStringTag]() { - return "SparseSet32"; - } - - copy() { - return this.__copyTo(new SparseSet32(0)); - } - - empty() { - return new SparseSet32(this.capacity); - } -} - -/** - * Creates a new sparse set with given max. capacity (max ID + 1) and - * chooses most memory efficient implementation, e.g. if `n` <= 256 - * returns a {@link SparseSet8} instance. - * - * @param n - max capacity, ID range: [0...n) - */ -export const defSparseSet = (n: number) => - n <= 0x100 - ? new SparseSet8(n) - : n <= 0x10000 - ? new SparseSet16(n) - : new SparseSet32(n); diff --git a/packages/associative/test/sparseset.test.ts b/packages/associative/test/sparseset.test.ts deleted file mode 100644 index ee57a9e2dd..0000000000 --- a/packages/associative/test/sparseset.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { isSet } from "@thi.ng/checks"; -import { equiv } from "@thi.ng/equiv"; -import { beforeEach, expect, test } from "bun:test"; -import { - SparseSet16, - SparseSet32, - SparseSet8, - defSparseSet, -} from "../src/index.js"; - -let set: SparseSet8; - -beforeEach(() => { - set = new SparseSet8(8); -}); - -test("factory / max value", () => { - let a = defSparseSet(0x100); - a.into([0xff, 0x100]); - expect(a instanceof SparseSet8).toBeTrue(); - expect([...a]).toEqual([0xff]); - - a = defSparseSet(0x10000); - a.into([0xffff, 0x10000]); - expect(a instanceof SparseSet16).toBeTrue(); - expect([...a]).toEqual([0xffff]); - - a = defSparseSet(0x10001); - a.into([0x10000, 0x10001]); - expect(a instanceof SparseSet32).toBeTrue(); - expect([...a]).toEqual([0x10000]); -}); - -test("ctor(n)", () => { - expect(isSet(set)).toBeTrue(); - expect(set.size).toBe(0); - expect(set.capacity).toBe(8); -}); - -test("ctor(arrays)", () => { - const d = new Uint8Array(8); - const s = new Uint8Array(8); - set = new SparseSet8(d, s); - expect(set.size).toBe(0); - expect(set.capacity).toBe(8); - expect(() => new SparseSet8(new Uint8Array(4), s)).toThrow(); -}); - -test("add", () => { - expect( - equiv( - set.into([1, 4, 3, 7, 9, 2, 0, 1, 2]), - new Set([0, 1, 2, 3, 4, 7]) - ) - ).toBeTrue(); -}); - -test("delete", () => { - set.into([1, 4, 3, 7, 9, 2, 0, 1, 2]); - expect(set.delete(4)).toBeTrue(); - expect(equiv(set, new Set([0, 1, 2, 3, 7]))).toBeTrue(); - expect(set.delete(0)).toBeTrue(); - expect(equiv(set, new Set([1, 2, 3, 7]))).toBeTrue(); - expect(set.delete(7)).toBeTrue(); - expect(equiv(set, new Set([1, 2, 3]))).toBeTrue(); - expect(set.delete(7)).toBeFalse(); - expect(set.delete(4)).toBeFalse(); - set.add(4); - expect(equiv(set, new Set([1, 2, 3, 4]))).toBeTrue(); -}); - -test("has", () => { - expect(set.has(0)).toBeFalse(); - set.add(0); - set.add(0); - expect(set.has(0)).toBeTrue(); - set.delete(0); - expect(set.has(0)).toBeFalse(); - set.into([3, 1, 2]); -});