interface: new BigIntOrderedMap

New ordered map implementation that is faster and more space
efficient. Stores the map and keys as a flat POJO, sorting them upon
iteration. The sorting cost is only typically paid once per render
however as the sorted array is cached until the map is mutated. This
implementation appears to play nicer with immer's structural sharing,
reducing the incidence of 'props are equal by value, but not by
reference'.
This commit is contained in:
Liam Fitzgerald 2021-04-20 13:31:57 +10:00
parent 1ab925f84c
commit fc955ab83e
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
2 changed files with 64 additions and 201 deletions

View File

@ -1,8 +1,10 @@
import produce from "immer"; import produce, { setAutoFreeze } from "immer";
import { compose } from "lodash/fp"; import { compose } from "lodash/fp";
import create, { State, UseStore } from "zustand"; import create, { State, UseStore } from "zustand";
import { persist, devtools } from "zustand/middleware"; import { persist, devtools } from "zustand/middleware";
setAutoFreeze(false);
export const stateSetter = <StateType>( export const stateSetter = <StateType>(
fn: (state: StateType) => void, fn: (state: StateType) => void,

View File

@ -1,227 +1,61 @@
import { BigInteger } from "big-integer"; import _ from 'lodash';
import { immerable } from 'immer'; import bigInt, { BigInteger } from "big-integer";
interface NonemptyNode<V> { function sortBigInt(a: BigInteger, b: BigInteger) {
n: [BigInteger, V]; if (a.lt(b)) {
l: MapNode<V>; return 1;
r: MapNode<V>; } else if (a.eq(b)) {
return 0;
} else {
return -1;
}
} }
type MapNode<V> = NonemptyNode<V> | null;
/**
* An implementation of ordered maps for JS
* Plagiarised wholesale from sys/zuse
*/
export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> { export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
private root: MapNode<V> = null; private root: Record<string, V> = {}
[immerable] = true; private cachedIter: [BigInteger, V][] | null = null;
size: number = 0;
constructor(initial: [BigInteger, V][] = []) { constructor(items: [BigInteger, V][] = []) {
initial.forEach(([key, val]) => { _.forEach(items, ([key, val]) => {
this.set(key, val); this.set(key, val);
}); });
this.generateCachedIter();
} }
/** get size() {
* Retrieve an value for a key return this.cachedIter?.length ?? Object.keys(this.root).length;
*/
get(key: BigInteger): V | null {
const inner = (node: MapNode<V>): V | null => {
if (!node) {
return null;
}
const [k, v] = node.n;
if (key.eq(k)) {
return v;
}
if (key.gt(k)) {
return inner(node.l);
} else {
return inner(node.r);
}
};
return inner(this.root);
} }
/**
* Put an item by a key
*/
set(key: BigInteger, value: V): void {
const inner = (node: MapNode<V>): MapNode<V> => { get(key: BigInteger) {
if (!node) { return this.root[key.toString()] ?? null;
return { }
n: [key, value],
l: null, set(key: BigInteger, value: V) {
r: null, this.root[key.toString()] = value;
}; this.cachedIter = null;
}
const [k] = node.n;
if (key.eq(k)) {
this.size--;
return {
...node,
n: [k, value],
};
}
if (key.gt(k)) {
const l = inner(node.l);
if (!l) {
throw new Error("invariant violation");
}
return {
...node,
l,
};
}
const r = inner(node.r);
if (!r) {
throw new Error("invariant violation");
}
return { ...node, r };
};
this.size++;
this.root = inner(this.root);
} }
/**
* Remove all entries
*/
clear() { clear() {
this.root = null; this.cachedIter = null;
this.root = {}
} }
/** has(key: BigInteger) {
* Predicate testing if map contains key return key.toString() in this.root;
*/
has(key: BigInteger): boolean {
const inner = (node: MapNode<V>): boolean => {
if (!node) {
return false;
}
const [k] = node.n;
if (k.eq(key)) {
return true;
}
if (key.gt(k)) {
return inner(node.l);
}
return inner(node.r);
};
return inner(this.root);
} }
/**
* Remove value associated with key, returning whether that key
* existed in the first place
*/
delete(key: BigInteger) { delete(key: BigInteger) {
const inner = (node: MapNode<V>): [boolean, MapNode<V>] => { const had = this.has(key);
if (!node) { if(had) {
return [false, null]; delete this.root[key.toString()];
} this.cachedIter = null;
const [k] = node.n;
if (k.eq(key)) {
return [true, this.nip(node)];
}
if (key.gt(k)) {
const [bool, l] = inner(node.l);
return [
bool,
{
...node,
l,
},
];
}
const [bool, r] = inner(node.r);
return [
bool,
{
...node,
r,
},
];
};
const [ret, newRoot] = inner(this.root);
if(ret) {
this.size--;
} }
this.root = newRoot; return had;
return ret;
}
private nip(nod: NonemptyNode<V>): MapNode<V> {
const inner = (node: NonemptyNode<V>): MapNode<V> => {
if (!node.l) {
return node.r;
}
if (!node.r) {
return node.l;
}
return {
...node.l,
r: inner(node.r),
};
};
return inner(nod);
}
peekLargest(): [BigInteger, V] | undefined {
const inner = (node: MapNode<V>): [BigInteger, V] | undefined => {
if(!node) {
return undefined;
}
if(node.l) {
return inner(node.l);
}
return node.n;
}
return inner(this.root);
}
peekSmallest(): [BigInteger, V] | undefined {
const inner = (node: MapNode<V>): [BigInteger, V] | undefined => {
if(!node) {
return undefined;
}
if(node.r) {
return inner(node.r);
}
return node.n;
}
return inner(this.root);
}
keys(): BigInteger[] {
const list = Array.from(this);
return list.map(([key]) => key);
}
forEach(f: (value: V, key: BigInteger) => void) {
const list = Array.from(this);
return list.forEach(([k,v]) => f(v,k));
} }
[Symbol.iterator](): IterableIterator<[BigInteger, V]> { [Symbol.iterator](): IterableIterator<[BigInteger, V]> {
let result: [BigInteger, V][] = [];
const inner = (node: MapNode<V>) => {
if (!node) {
return;
}
inner(node.l);
result.push(node.n);
inner(node.r);
};
inner(this.root);
let idx = 0; let idx = 0;
const result = this.generateCachedIter();
return { return {
[Symbol.iterator]: this[Symbol.iterator], [Symbol.iterator]: this[Symbol.iterator],
next: (): IteratorResult<[BigInteger, V]> => { next: (): IteratorResult<[BigInteger, V]> => {
@ -232,4 +66,31 @@ export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
}, },
}; };
} }
peekLargest() {
const sorted = Array.from(this);
return sorted[0] as [BigInteger, V] | null;
}
peekSmallest() {
const sorted = Array.from(this);
return sorted[sorted.length - 1] as [BigInteger, V] | null;
}
keys() {
return Object.keys(this.root).map(k => bigInt(k)).sort(sortBigInt)
}
private generateCachedIter() {
if(this.cachedIter) {
return this.cachedIter;
}
const result = Object.keys(this.root).map(key => {
const num = bigInt(key);
return [num, this.root[key]] as [BigInteger, V];
}).sort(([a], [b]) => sortBigInt(a,b));
this.cachedIter = result;
return result;
}
} }