mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 08:32:39 +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 create, { State, UseStore } from "zustand";
|
||||
import { persist, devtools } from "zustand/middleware";
|
||||
|
||||
setAutoFreeze(false);
|
||||
|
||||
|
||||
export const stateSetter = <StateType>(
|
||||
fn: (state: StateType) => void,
|
||||
|
@ -1,227 +1,61 @@
|
||||
import { BigInteger } from "big-integer";
|
||||
import { immerable } from 'immer';
|
||||
import _ from 'lodash';
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
|
||||
interface NonemptyNode<V> {
|
||||
n: [BigInteger, V];
|
||||
l: MapNode<V>;
|
||||
r: MapNode<V>;
|
||||
function sortBigInt(a: BigInteger, b: BigInteger) {
|
||||
if (a.lt(b)) {
|
||||
return 1;
|
||||
} 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]> {
|
||||
private root: MapNode<V> = null;
|
||||
[immerable] = true;
|
||||
size: number = 0;
|
||||
private root: Record<string, V> = {}
|
||||
private cachedIter: [BigInteger, V][] | null = null;
|
||||
|
||||
constructor(initial: [BigInteger, V][] = []) {
|
||||
initial.forEach(([key, val]) => {
|
||||
constructor(items: [BigInteger, V][] = []) {
|
||||
_.forEach(items, ([key, val]) => {
|
||||
this.set(key, val);
|
||||
});
|
||||
this.generateCachedIter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an value for a key
|
||||
*/
|
||||
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);
|
||||
get size() {
|
||||
return this.cachedIter?.length ?? Object.keys(this.root).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an item by a key
|
||||
*/
|
||||
set(key: BigInteger, value: V): void {
|
||||
|
||||
const inner = (node: MapNode<V>): MapNode<V> => {
|
||||
if (!node) {
|
||||
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 };
|
||||
};
|
||||
this.size++;
|
||||
this.root = inner(this.root);
|
||||
get(key: BigInteger) {
|
||||
return this.root[key.toString()] ?? null;
|
||||
}
|
||||
|
||||
set(key: BigInteger, value: V) {
|
||||
this.root[key.toString()] = value;
|
||||
this.cachedIter = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all entries
|
||||
*/
|
||||
clear() {
|
||||
this.root = null;
|
||||
this.cachedIter = null;
|
||||
this.root = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate testing if map contains key
|
||||
*/
|
||||
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);
|
||||
has(key: BigInteger) {
|
||||
return key.toString() in this.root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove value associated with key, returning whether that key
|
||||
* existed in the first place
|
||||
*/
|
||||
delete(key: BigInteger) {
|
||||
const inner = (node: MapNode<V>): [boolean, MapNode<V>] => {
|
||||
if (!node) {
|
||||
return [false, 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--;
|
||||
const had = this.has(key);
|
||||
if(had) {
|
||||
delete this.root[key.toString()];
|
||||
this.cachedIter = null;
|
||||
}
|
||||
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));
|
||||
return had;
|
||||
}
|
||||
|
||||
[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;
|
||||
const result = this.generateCachedIter();
|
||||
return {
|
||||
[Symbol.iterator]: this[Symbol.iterator],
|
||||
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