UNPKG

@thi.ng/associative

Version:

ES Map/Set-compatible implementations with customizable equality semantics & supporting operations

208 lines (207 loc) 5.17 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; import { ceilPow2 } from "@thi.ng/binary/pow"; import { isPlainObject } from "@thi.ng/checks/is-plain-object"; import { equiv } from "@thi.ng/equiv"; import { map } from "@thi.ng/transducers/map"; import { dissoc } from "./dissoc.js"; import { __equivMap } from "./internal/equiv.js"; import { __inspectable } from "./internal/inspect.js"; import { into } from "./into.js"; const __private = /* @__PURE__ */ new WeakMap(); const __iterator = (map2, id) => function* () { for (let p of __private.get(map2).bins) { if (p) yield p[id]; } }; const DEFAULT_CAP = 16; const DEFAULT_LOAD = 0.75; let HashMap = class extends Map { constructor(pairs, opts) { super(); const m = ceilPow2(Math.min(opts.cap || DEFAULT_CAP, 4)) - 1; __private.set(this, { hash: opts.hash, equiv: opts.equiv || equiv, load: opts.load || DEFAULT_LOAD, mask: m, bins: new Array(m + 1), size: 0 }); if (pairs) { this.into(pairs); } } get [Symbol.species]() { return HashMap; } get [Symbol.toStringTag]() { return "HashMap"; } get size() { return __private.get(this).size; } [Symbol.iterator]() { return this.entries(); } *entries() { for (let p of __private.get(this).bins) { if (p) yield [p[0], p[1]]; } } keys() { return __iterator(this, 0)(); } values() { return __iterator(this, 1)(); } /** * The key & value args given the callback `fn` MUST be treated as * readonly/immutable. This could be enforced via TS, but would * break ES6 Map interface contract. * * @param fn - * @param thisArg - */ forEach(fn, thisArg) { for (let pair of __private.get(this).bins) { fn.call(thisArg, pair[1], pair[0], this); } } clear() { const $this = __private.get(this); $this.bins = new Array(DEFAULT_CAP); $this.mask = 15; $this.size = 0; } empty() { return new HashMap(null, this.opts({ cap: DEFAULT_CAP })); } copy() { const $this = __private.get(this); const m = new HashMap(null, this.opts({ cap: 4 })); Object.assign(__private.get(m), { bins: $this.bins.slice(), mask: $this.mask, size: $this.size }); return m; } equiv(o) { return __equivMap(this, o); } has(key) { const $this = __private.get(this); const i = this.find(key, $this); return i >= 0 && $this.bins[i] != void 0; } get(key, notFound) { const $this = __private.get(this); const i = this.find(key, $this); return i >= 0 && $this.bins[i] ? $this.bins[i][1] : notFound; } set(key, val) { const $this = __private.get(this); let i = this.find(key, $this); if (i >= 0 && $this.bins[i]) { $this.bins[i][1] = val; return this; } if ($this.size > $this.mask * $this.load) { this.resize($this); i = this.find(key, $this); } $this.bins[i] = [key, val]; $this.size++; return this; } delete(key) { const $this = __private.get(this); const { bins, mask } = $this; let i = this.find(key, $this); if (i >= 0 && !bins[i]) { return false; } $this.size--; let j = i; let k; while (true) { delete bins[i]; do { j = j + 1 & mask; if (!bins[j]) return true; k = $this.hash(bins[j][0]) & mask; } while (i <= j ? i < k && k <= j : i < k || k <= j); bins[i] = bins[j]; i = j; } } into(pairs) { return into(this, pairs); } dissoc(keys) { return dissoc(this, keys); } opts(overrides) { const $this = __private.get(this); return { hash: $this.hash, equiv: $this.equiv, load: $this.load, cap: $this.mask + 1, ...overrides }; } /** @internal */ find(key, $this) { const { bins, equiv: equiv2, mask } = $this; let i = mask; let h = $this.hash(key) & mask; while (bins[h] && !equiv2(bins[h][0], key)) { i--; if (i < 0) return -1; h = h + 1 & mask; } return h; } /** @internal */ resize($this) { const src = $this.bins; const cap = ($this.mask + 1) * 2; $this.bins = new Array(cap); $this.mask = cap - 1; $this.size = 0; for (let p of src) { if (p) this.set(p[0], p[1]); } } }; HashMap = __decorateClass([ __inspectable ], HashMap); function defHashMap(src, opts) { if (isPlainObject(src)) { const keys = Object.keys(src); return new HashMap( map((k) => [k, src[k]], keys), { cap: keys.length / (opts.load || DEFAULT_LOAD), ...opts } ); } else { return new HashMap(src, opts); } } export { HashMap, defHashMap };