@thi.ng/sorted-map
Version:
Skiplist-based sorted map & set implementation
325 lines (324 loc) • 8.35 kB
JavaScript
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 { dissoc } from "@thi.ng/associative/dissoc";
import { __equivMap } from "@thi.ng/associative/internal/equiv";
import { __inspectable } from "@thi.ng/associative/internal/inspect";
import { into } from "@thi.ng/associative/into";
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
import { compare } from "@thi.ng/compare/compare";
import { SYSTEM } from "@thi.ng/random/system";
import { map } from "@thi.ng/transducers/map";
import { isReduced } from "@thi.ng/transducers/reduced";
class Node {
constructor(k, v, level = 0) {
this.k = k;
this.v = v;
this.level = level;
}
next;
prev;
up;
down;
}
const __private = /* @__PURE__ */ new WeakMap();
let SortedMap = class extends Map {
constructor(pairs, opts = {}) {
super();
__private.set(this, {
head: new Node(),
cmp: opts.compare || compare,
rnd: opts.rnd || SYSTEM,
p: opts.probability || 1 / Math.E,
size: 0
});
if (pairs) {
this.into(pairs);
}
}
get size() {
return __private.get(this).size;
}
get [Symbol.species]() {
return SortedMap;
}
*[Symbol.iterator]() {
let node = this.firstNode();
while (node?.k !== void 0) {
yield [node.k, node.v];
node = node.next;
}
}
/**
* Yields iterator of sorted `[key, value]` pairs, optionally taking given
* `key` and `max` flag into account.
*
* @remarks
* If `key` is given and `max=false`, the key is used as minimum search key
* and the iterator will only yield pairs for which keys are `>=` given key.
* If `max=true`, the given is used as maximum and only yields pairs for
* which keys are `<=` given key.
*
* If **no** key is given, yields **all** pairs.
*
* @param key
* @param max
*/
*entries(key, max = false) {
if (key === void 0) {
yield* this;
return;
}
const { cmp, size } = __private.get(this);
if (!size) return;
if (max) {
let node = this.firstNode();
while (node?.k !== void 0 && cmp(node.k, key) <= 0) {
yield [node.k, node.v];
node = node.next;
}
} else {
let node = this.firstNode();
while (node.down) node = node.down;
while (node) {
if (node.k !== void 0 && cmp(node.k, key) >= 0) {
yield [node.k, node.v];
}
node = node.next;
}
}
}
/**
* Similar to {@link SortedMap.entries}, but only yield sequence of keys.
*
* @param key
* @param max
*/
keys(key, max = false) {
return map((p) => p[0], this.entries(key, max));
}
/**
* Similar to {@link SortedMap.entries}, but only yield sequence of values.
*
* @param key
* @param max
*/
values(key, max = false) {
return map((p) => p[1], this.entries(key, max));
}
clear() {
const $this = __private.get(this);
$this.head = new Node();
$this.size = 0;
}
empty() {
return new SortedMap(null, this.opts());
}
copy() {
return new SortedMap(this, this.opts());
}
compare(o) {
const n = this.size;
const m = o.size;
if (n < m) return -1;
if (n > m) return 1;
const i = this.entries();
const j = o.entries();
let x, y;
let c;
while (x = i.next(), y = j.next(), !x.done && !y.done) {
if ((c = compare(x.value[0], y.value[0])) !== 0 || (c = compare(x.value[1], y.value[1])) !== 0) {
return c;
}
}
return 0;
}
equiv(o) {
return __equivMap(this, o);
}
first() {
const node = this.firstNode();
return node?.k !== void 0 ? [node.k, node.v] : void 0;
}
get(key, notFound) {
const $this = __private.get(this);
const node = this.findNode(key);
return node.k !== void 0 && $this.cmp(node.k, key) === 0 ? node.v : notFound;
}
has(key) {
const { cmp } = __private.get(this);
const node = this.findNode(key);
return node.k !== void 0 && cmp(node.k, key) === 0;
}
set(key, val) {
const $this = __private.get(this);
const { cmp, p, rnd } = $this;
let node = this.findNode(key);
if (node.k !== void 0 && cmp(node.k, key) === 0) {
node.v = val;
while (node.down) {
node = node.down;
node.v = val;
}
return this;
}
let newNode = new Node(key, val, node.level);
this.insertInLane(node, newNode);
let currLevel = node.level;
let headLevel = $this.head.level;
while (rnd.probability(p)) {
if (currLevel >= headLevel) {
const newHead = new Node(
void 0,
void 0,
headLevel + 1
);
this.linkLanes(newHead, $this.head);
$this.head = newHead;
headLevel++;
}
while (!node.up) node = node.prev;
node = node.up;
const tmp = new Node(key, val, node.level);
this.insertInLane(node, tmp);
this.linkLanes(tmp, newNode);
newNode = tmp;
currLevel++;
}
$this.size++;
return this;
}
delete(key) {
const $this = __private.get(this);
let node = this.findNode(key);
if (node.k === void 0 || $this.cmp(node.k, key) !== 0) return false;
while (node.down) node = node.down;
let prev;
let next;
while (node) {
prev = node.prev;
next = node.next;
if (prev) prev.next = next;
if (next) next.prev = prev;
node = node.up;
}
while (!$this.head.next && $this.head.down) {
$this.head = $this.head.down;
$this.head.up = void 0;
}
$this.size--;
return true;
}
into(pairs) {
return into(this, pairs);
}
dissoc(keys) {
return dissoc(this, keys);
}
/**
* 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 p of this) {
fn.call(thisArg, p[1], p[0], this);
}
}
$reduce(rfn, acc) {
let node = this.firstNode();
while (node?.k !== void 0 && !isReduced(acc)) {
acc = rfn(acc, [node.k, node.v]);
node = node.next;
}
return acc;
}
opts() {
const $this = __private.get(this);
return {
compare: $this.cmp,
probability: $this.p,
rnd: $this.rnd
};
}
/**
* Inserts `b` as successor of `a` (in the same lane as `a`).
*
* @param a
* @param b
*/
insertInLane(a, b) {
b.prev = a;
b.next = a.next;
if (a.next) a.next.prev = b;
a.next = b;
}
/**
* Links lanes by connecting `a` and `b` vertically.
*
* @param a
* @param b
*/
linkLanes(a, b) {
a.down = b;
b.up = a;
}
/**
* Returns first node on lowest level. Unless the map is empty, this node
* will be the first data node (with the smallest key).
*/
firstNode() {
const { head } = __private.get(this);
let node = head;
while (node.down) node = node.down;
while (node.prev) node = node.prev;
if (node.next) node = node.next;
return node;
}
/**
* Returns the first matching (or predecessor) node for given key (NOT
* necessarily at the lowest level).
*
* @param key
*/
findNode(key) {
let { cmp, head } = __private.get(this);
let node = head;
let next;
let down;
let nodeKey;
while (true) {
next = node.next;
while (next && cmp(next.k, key) <= 0) {
node = next;
next = node.next;
}
nodeKey = node.k;
if (nodeKey !== void 0 && cmp(nodeKey, key) === 0) break;
down = node.down;
if (!down) break;
node = down;
}
return node;
}
};
SortedMap = __decorateClass([
__inspectable
], SortedMap);
function defSortedMap(src, opts) {
return isPlainObject(src) ? new SortedMap(Object.entries(src), opts) : new SortedMap(src, opts);
}
export {
SortedMap,
defSortedMap
};