mirror of
https://github.com/urbit/shrub.git
synced 2024-12-22 10:21:31 +03:00
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:
parent
1ab925f84c
commit
fc955ab83e
@ -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,
|
||||||
|
@ -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,
|
|
||||||
r: 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 };
|
set(key: BigInteger, value: V) {
|
||||||
};
|
this.root[key.toString()] = value;
|
||||||
this.size++;
|
this.cachedIter = null;
|
||||||
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;
|
return had;
|
||||||
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 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user