├── .gitignore ├── README.md ├── browser_test └── main.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── ArrayChildPtr.ts ├── ArtIterator.ts ├── ArtNode.ts ├── ArtNode16.ts ├── ArtNode256.ts ├── ArtNode4.ts ├── ArtNode48.ts ├── ArtTree.ts ├── ChildPtr.ts ├── Deque.ts ├── IterCallback.ts ├── Leaf.ts ├── PartNode.ts ├── index.ts └── utils.ts ├── test └── ArtTree.test.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | out_test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Persistent Adaptive Radix Tree (PART) for JavaScript 2 | 3 | The Persistent Adaptive Radix Tree (PART) is a trie with a high branching factor and adaptively-sized nodes based on [ART](https://db.in.tum.de/~leis/papers/ART.pdf). It provides efficient persistence using path copying and reference counting. In microbenchmarks, PART achieves throughput and space efficiency comparable to a mutable hash table while providing persistence, lower variance in operation latency, and efficient union, intersection, and range scan operations. 4 | 5 | This repository contains a JavaScript implementation of PART based on [part](https://github.com/ankurdave/part). 6 | -------------------------------------------------------------------------------- /browser_test/main.js: -------------------------------------------------------------------------------- 1 | const ArtTree = artTree.ArtTree 2 | 3 | const maxN = 5000 4 | const maxKeyLen = 10 5 | 6 | const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min 7 | const range = n => new Array(n).fill(n) 8 | 9 | const genKeys = () => { 10 | let seq = range(maxN) 11 | .map(() => 12 | range(randInt(5, maxKeyLen)) 13 | .map(() => randInt(0, 255) + 1) 14 | .concat(0), 15 | ) 16 | .map(s => s.join(",")) 17 | seq = new Set(seq) 18 | return Array.from(seq).map(s => s.split(/,/g).map(s => +s)) 19 | } 20 | 21 | const test = (name, fn) => { 22 | console.log(name) 23 | fn() 24 | } 25 | 26 | test("insert, search", () => { 27 | const t = new ArtTree() 28 | const keys = genKeys() 29 | const holdOut = 10 30 | 31 | const start = performance.now() 32 | 33 | for (let i = 0; i < keys.length - holdOut; i++) { 34 | const k = keys[i] 35 | console.assert(t.search(k) === null) 36 | t.insert(k, i) 37 | console.assert(t.search(k) === i) 38 | } 39 | 40 | console.assert(t.size === keys.length - holdOut) 41 | 42 | for (let i = keys.length - holdOut; i < keys.length; i++) { 43 | console.assert(t.search(keys[i]) === null) 44 | } 45 | 46 | console.log(`${performance.now() - start} ms`) 47 | console.log(`${keys.length} nodes`) 48 | console.log("==========================") 49 | }) 50 | 51 | test("insert, delete", () => { 52 | const t = new ArtTree() 53 | const keys = genKeys() 54 | 55 | const start = performance.now() 56 | 57 | for (let i = 0; i < keys.length; i++) { 58 | const k = keys[i] 59 | console.assert(t.search(k) === null) 60 | t.insert(k, i) 61 | console.assert(t.search(k) === i) 62 | } 63 | 64 | console.assert(t.size === keys.length) 65 | 66 | for (let i = 0; i < keys.length; i++) { 67 | const k = keys[i] 68 | console.assert(t.search(k) === i) 69 | t.delete(k) 70 | console.assert(t.search(k) === null) 71 | } 72 | 73 | console.assert(t.size === 0, t.size, 0) 74 | 75 | console.log(`${performance.now() - start} ms`) 76 | console.log(`${keys.length} nodes`) 77 | console.log("==========================") 78 | }) 79 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "part-js", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "out/index.js", 6 | "scripts": { 7 | "build": "npm run build-ts && npm run build-browser", 8 | "build-ts": "tsc", 9 | "build-browser": 10 | "rollup ./out/index.js --output.format iife --output.file ./out_test/ArtTree.js --name 'artTree' --sourcemap", 11 | "dev": "tsc -w", 12 | "test": "jest ./test", 13 | "test-watch": "jest --watch ./test" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/roman01la/part-js.git" 18 | }, 19 | "keywords": ["PART", "ART", "radix tree", "adaptive radix tree"], 20 | "author": "Roman Liutikov", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/roman01la/part-js/issues" 24 | }, 25 | "homepage": "https://github.com/roman01la/part-js#readme", 26 | "devDependencies": { 27 | "jest": "^22.1.2", 28 | "rollup": "^0.54.1", 29 | "typescript": "^2.6.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ArrayChildPtr.ts: -------------------------------------------------------------------------------- 1 | import { ChildPtr } from "./ChildPtr" 2 | import { PartNode } from "./PartNode" 3 | 4 | export class ArrayChildPtr extends ChildPtr { 5 | children: (PartNode | null)[] 6 | i: number 7 | 8 | constructor(children: (PartNode | null)[], i: number) { 9 | super() 10 | this.children = children 11 | this.i = i 12 | } 13 | 14 | public get(): PartNode | null { 15 | return this.children[this.i] 16 | } 17 | 18 | public set(n: PartNode): void { 19 | this.children[this.i] = n 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ArtIterator.ts: -------------------------------------------------------------------------------- 1 | import { PartNode } from "./PartNode" 2 | import { Deque } from "./Deque" 3 | import { Leaf } from "./Leaf" 4 | import { ArtNode } from "./ArtNode" 5 | 6 | export class ArtIterator implements Iterator<[number[], object]> { 7 | private elemStack: Deque = new Deque() 8 | private idxStack: Deque = new Deque() 9 | 10 | constructor(root: PartNode | null) { 11 | if (root) { 12 | this.elemStack.push(root) 13 | this.idxStack.push(0) 14 | this.maybeAdvance() 15 | } 16 | } 17 | 18 | public hasNext(): boolean { 19 | return this.elemStack.isEmpty() === false 20 | } 21 | 22 | public next(value?: any): IteratorResult<[number[], object]> { 23 | if (this.hasNext()) { 24 | const leaf = this.elemStack.peek() 25 | if (leaf) { 26 | const { key, value } = leaf 27 | this.idxStack.push(this.idxStack.pop() + 1) 28 | this.maybeAdvance() 29 | return { value: [key, value], done: false } 30 | } else { 31 | return { value: [[0], {}], done: false } 32 | } 33 | } else { 34 | return { value: value, done: true } 35 | } 36 | } 37 | 38 | public remove(): void { 39 | throw new Error("Unsupported operation") 40 | } 41 | 42 | private canPopExhausted(): boolean { 43 | let canDo = 44 | this.elemStack.isEmpty() === false ? this.elemStack.peek() : false 45 | return canDo instanceof PartNode 46 | ? canDo.exhausted(this.idxStack.peek()) 47 | : false 48 | } 49 | 50 | private maybeAdvance(): void { 51 | while (this.canPopExhausted()) { 52 | this.elemStack.pop() 53 | this.idxStack.pop() 54 | 55 | if (this.elemStack.isEmpty() === false) { 56 | this.idxStack.push(this.idxStack.pop() + 1) 57 | } 58 | } 59 | 60 | if (this.elemStack.isEmpty() === false) { 61 | while (true) { 62 | if (this.elemStack.peek() instanceof Leaf) { 63 | break 64 | } else { 65 | const curr = this.elemStack.peek() 66 | if (curr) { 67 | this.idxStack.push(curr.nextChildAtOrAfter(this.idxStack.pop())) 68 | } 69 | const idx = this.idxStack.peek() 70 | if (idx !== null && curr) { 71 | const child = curr.childAt(idx) 72 | if (child) { 73 | this.elemStack.push(child) 74 | this.idxStack.push(0) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ArtNode.ts: -------------------------------------------------------------------------------- 1 | import { PartNode } from "./PartNode" 2 | import { ArtNode4 } from "./ArtNode4" 3 | import { Leaf } from "./Leaf" 4 | import { ChildPtr } from "./ChildPtr" 5 | import { arrayCopy } from "./utils" 6 | 7 | export abstract class ArtNode extends PartNode { 8 | numChildren = 0 9 | partialLen = 0 10 | partial: number[] = new Array(PartNode.MAX_PREFIX_LEN).fill(0) 11 | 12 | constructor(other?: ArtNode) { 13 | super() 14 | 15 | if (other) { 16 | this.numChildren = other.numChildren 17 | this.partialLen = other.partialLen 18 | this.partial = arrayCopy( 19 | other.partial, 20 | 0, 21 | this.partial, 22 | 0, 23 | Math.min(PartNode.MAX_PREFIX_LEN, this.partialLen), 24 | ) 25 | } 26 | } 27 | 28 | public checkPrefix(key: number[], depth: number): number { 29 | const maxCmp = Math.min( 30 | Math.min(this.partialLen, PartNode.MAX_PREFIX_LEN), 31 | key.length - depth, 32 | ) 33 | 34 | let idx: number 35 | 36 | for (idx = 0; idx < maxCmp; idx++) { 37 | if (this.partial[idx] !== key[depth + idx]) { 38 | return idx 39 | } 40 | } 41 | 42 | return idx 43 | } 44 | 45 | public prefixMismatch(key: number[], depth: number): number { 46 | let maxCmp = Math.min( 47 | Math.min(this.partialLen, PartNode.MAX_PREFIX_LEN), 48 | key.length - depth, 49 | ) 50 | 51 | let idx: number 52 | 53 | for (idx = 0; idx < maxCmp; idx++) { 54 | if (this.partial[idx] !== key[depth + idx]) { 55 | return idx 56 | } 57 | } 58 | 59 | if (this.partialLen > PartNode.MAX_PREFIX_LEN) { 60 | const l = this.minimum() 61 | if (l) { 62 | maxCmp = Math.min(l.key.length, key.length) - depth 63 | 64 | for (; idx < maxCmp; idx++) { 65 | if (l.key[idx + depth] !== key[depth + idx]) { 66 | return idx 67 | } 68 | } 69 | } 70 | } 71 | 72 | return idx 73 | } 74 | 75 | public abstract findChild(c: number): ChildPtr | null 76 | 77 | public abstract addChild(ref: ChildPtr, c: number, child: PartNode): void 78 | 79 | public abstract removeChild(ref: ChildPtr, c: number): void 80 | 81 | public abstract nextChildAtOrAfter(i: number): number 82 | 83 | public abstract childAt(i: number): PartNode | null 84 | 85 | public insert( 86 | ref: ChildPtr, 87 | key: number[], 88 | value: object, 89 | depth: number, 90 | forceClone: boolean, 91 | ): boolean { 92 | const doClone = forceClone || this.refcount > 1 93 | 94 | if (this.partialLen > 0) { 95 | const prefixDiff: number = this.prefixMismatch(key, depth) 96 | 97 | if (prefixDiff >= this.partialLen) { 98 | depth += this.partialLen 99 | } else { 100 | const result = new ArtNode4() 101 | const oldRef = ref.get() 102 | 103 | ref.changeNoDecrement(result) 104 | result.partialLen = prefixDiff 105 | 106 | result.partial = arrayCopy( 107 | this.partial, 108 | 0, 109 | result.partial, 110 | 0, 111 | Math.min(PartNode.MAX_PREFIX_LEN, prefixDiff), 112 | ) 113 | 114 | const thisWritable: ArtNode = doClone ? this.clone() : this 115 | 116 | if (this.partialLen <= PartNode.MAX_PREFIX_LEN) { 117 | result.addChild(ref, thisWritable.partial[prefixDiff], thisWritable) 118 | thisWritable.partialLen -= prefixDiff + 1 119 | 120 | thisWritable.partial = arrayCopy( 121 | thisWritable.partial, 122 | prefixDiff + 1, 123 | thisWritable.partial, 124 | 0, 125 | Math.min(PartNode.MAX_PREFIX_LEN, thisWritable.partialLen), 126 | ) 127 | } else { 128 | thisWritable.partialLen -= prefixDiff + 1 129 | 130 | const l = this.minimum() 131 | 132 | if (l) { 133 | result.addChild(ref, l.key[depth + prefixDiff], thisWritable) 134 | 135 | thisWritable.partial = arrayCopy( 136 | l.key, 137 | depth + prefixDiff + 1, 138 | thisWritable.partial, 139 | 0, 140 | Math.min(PartNode.MAX_PREFIX_LEN, thisWritable.partialLen), 141 | ) 142 | } 143 | } 144 | 145 | const l = new Leaf(key, value) 146 | result.addChild(ref, key[depth + prefixDiff], l) 147 | 148 | if (oldRef) { 149 | oldRef.decrementRefcount() 150 | } 151 | 152 | return true 153 | } 154 | } 155 | 156 | const thisWritable: ArtNode = doClone ? this.clone() : this 157 | 158 | if (doClone) { 159 | ref.change(thisWritable) 160 | } 161 | 162 | const child = thisWritable.findChild(key[depth]) 163 | 164 | if (child) { 165 | return PartNode.insert( 166 | child.get(), 167 | child, 168 | key, 169 | value, 170 | depth + 1, 171 | forceClone, 172 | ) 173 | } else { 174 | const l = new Leaf(key, value) 175 | thisWritable.addChild(ref, key[depth], l) 176 | return true 177 | } 178 | } 179 | 180 | public delete( 181 | ref: ChildPtr, 182 | key: number[], 183 | depth: number, 184 | forceClone: boolean, 185 | ): boolean { 186 | if (this.partialLen > 0) { 187 | const prefixLen = this.checkPrefix(key, depth) 188 | if (prefixLen !== Math.min(PartNode.MAX_PREFIX_LEN, this.partialLen)) { 189 | return false 190 | } 191 | depth += this.partialLen 192 | } 193 | 194 | const doClone = forceClone || this.refcount > 1 195 | 196 | const thisWritable: ArtNode = doClone ? this.clone() : this 197 | 198 | const child = thisWritable.findChild(key[depth]) 199 | 200 | if (!child) { 201 | return false 202 | } 203 | 204 | if (doClone) { 205 | ref.change(thisWritable) 206 | } 207 | 208 | const node = child.get() 209 | const childIsLeaf = node instanceof Leaf 210 | const doDelete = node ? node.delete(child, key, depth + 1, doClone) : false 211 | 212 | if (doDelete && childIsLeaf) { 213 | thisWritable.removeChild(ref, key[depth]) 214 | } 215 | 216 | return doDelete 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/ArtNode16.ts: -------------------------------------------------------------------------------- 1 | import { Leaf } from "./Leaf" 2 | import { ArtNode } from "./ArtNode" 3 | import { ArtNode4 } from "./ArtNode4" 4 | import { ArtNode48 } from "./ArtNode48" 5 | import { PartNode } from "./PartNode" 6 | import { ChildPtr } from "./ChildPtr" 7 | import { ArrayChildPtr } from "./ArrayChildPtr" 8 | import { arrayCopy } from "./utils" 9 | 10 | export class ArtNode16 extends ArtNode { 11 | public static count = 0 12 | 13 | keys: number[] = new Array(16).fill(0) 14 | children: (PartNode | null)[] = new Array(16).fill(null) 15 | 16 | constructor(other?: ArtNode4 | ArtNode16 | ArtNode48) { 17 | super(other) 18 | 19 | if (other instanceof ArtNode16) { 20 | this.keys = arrayCopy(other.keys, 0, this.keys, 0, other.numChildren) 21 | for (let i = 0; i < other.numChildren; i++) { 22 | this.children[i] = other.children[i] 23 | const child = this.children[i] 24 | if (child) { 25 | child.refcount++ 26 | } 27 | } 28 | ArtNode16.count++ 29 | } 30 | 31 | if (other instanceof ArtNode4) { 32 | ArtNode16.count++ 33 | this.numChildren = other.numChildren 34 | this.partialLen = other.partialLen 35 | this.keys = arrayCopy(other.keys, 0, this.keys, 0, this.numChildren) 36 | for (let i = 0; i < this.numChildren; i++) { 37 | this.children[i] = other.children[i] 38 | const child = this.children[i] 39 | if (child) { 40 | child.refcount++ 41 | } 42 | } 43 | } 44 | 45 | if (other instanceof ArtNode48) { 46 | ArtNode16.count++ 47 | 48 | console.assert(other.numChildren <= 16) 49 | 50 | this.numChildren = other.numChildren 51 | this.partialLen = other.partialLen 52 | this.partial = arrayCopy( 53 | other.partial, 54 | 0, 55 | this.partial, 56 | 0, 57 | Math.min(PartNode.MAX_PREFIX_LEN, this.partialLen), 58 | ) 59 | let child = 0 60 | for (let i = 0; i < 256; i++) { 61 | let pos = other.keys[i] 62 | if (pos !== 0) { 63 | this.keys[child] = i 64 | this.children[child] = other.children[pos - 1] 65 | const node = this.children[child] 66 | if (node) { 67 | node.refcount++ 68 | } 69 | child++ 70 | } 71 | } 72 | } 73 | } 74 | 75 | public clone(): PartNode { 76 | return new ArtNode16(this) 77 | } 78 | 79 | public findChild(c: number): ChildPtr | null { 80 | for (let i = 0; i < this.numChildren; i++) { 81 | if (this.keys[i] === c) { 82 | return new ArrayChildPtr(this.children, i) 83 | } 84 | } 85 | return null 86 | } 87 | 88 | public minimum(): Leaf | null { 89 | return PartNode.minimum(this.children[0]) 90 | } 91 | 92 | public addChild(ref: ChildPtr, c: number, child: PartNode): void { 93 | console.assert(this.refcount <= 1) 94 | 95 | if (this.numChildren < 16) { 96 | let idx: number 97 | for (idx = 0; idx < this.numChildren; idx++) { 98 | if (c < this.keys[idx]) { 99 | break 100 | } 101 | } 102 | 103 | this.keys = arrayCopy( 104 | this.keys, 105 | idx, 106 | this.keys, 107 | idx + 1, 108 | this.numChildren - idx, 109 | ) 110 | 111 | this.children = arrayCopy( 112 | this.children, 113 | idx, 114 | this.children, 115 | idx + 1, 116 | this.numChildren - idx, 117 | ) 118 | 119 | this.keys[idx] = c 120 | this.children[idx] = child 121 | child.refcount++ 122 | this.numChildren++ 123 | } else { 124 | const result = new ArtNode48(this) 125 | ref.change(result) 126 | result.addChild(ref, c, child) 127 | } 128 | } 129 | 130 | public removeChild(ref: ChildPtr, c: number): void { 131 | let idx: number 132 | for (idx = 0; idx < this.numChildren; idx++) { 133 | if (c === this.keys[idx]) { 134 | break 135 | } 136 | } 137 | if (idx === this.numChildren) { 138 | return 139 | } 140 | 141 | const child = this.children[idx] 142 | if (child) { 143 | child.decrementRefcount() 144 | } 145 | 146 | this.keys = arrayCopy( 147 | this.keys, 148 | idx + 1, 149 | this.keys, 150 | idx, 151 | this.numChildren - idx - 1, 152 | ) 153 | 154 | this.children = arrayCopy( 155 | this.children, 156 | idx + 1, 157 | this.children, 158 | idx, 159 | this.numChildren - idx - 1, 160 | ) 161 | 162 | this.numChildren-- 163 | 164 | if (this.numChildren === 3) { 165 | const result = new ArtNode4(this) 166 | ref.change(result) 167 | } 168 | } 169 | 170 | public exhausted(i: number): boolean { 171 | return i >= this.numChildren 172 | } 173 | 174 | public nextChildAtOrAfter(i: number): number { 175 | return i 176 | } 177 | 178 | public childAt(i: number): PartNode | null { 179 | return this.children[i] 180 | } 181 | 182 | public decrementRefcount(): number { 183 | if (--this.refcount <= 0) { 184 | let freed = 0 185 | for (let i = 0; i < this.numChildren; i++) { 186 | const child = this.children[i] 187 | if (child) { 188 | freed += child.decrementRefcount() 189 | } 190 | } 191 | ArtNode16.count-- 192 | return freed + 232 193 | } 194 | return 0 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/ArtNode256.ts: -------------------------------------------------------------------------------- 1 | import { Leaf } from "./Leaf" 2 | import { ArtNode } from "./ArtNode" 3 | import { ArtNode48 } from "./ArtNode48" 4 | import { PartNode } from "./PartNode" 5 | import { ChildPtr } from "./ChildPtr" 6 | import { ArrayChildPtr } from "./ArrayChildPtr" 7 | import { arrayCopy } from "./utils" 8 | 9 | export class ArtNode256 extends ArtNode { 10 | public static count = 0 11 | 12 | children: (PartNode | null)[] = new Array(256).fill(null) 13 | 14 | constructor(other?: ArtNode48 | ArtNode256) { 15 | super(other) 16 | 17 | if (other instanceof ArtNode48) { 18 | ArtNode256.count++ 19 | 20 | this.numChildren = other.numChildren 21 | this.partialLen = other.partialLen 22 | 23 | this.partial = arrayCopy( 24 | other.partial, 25 | 0, 26 | this.partial, 27 | 0, 28 | Math.min(PartNode.MAX_PREFIX_LEN, this.partialLen), 29 | ) 30 | 31 | for (let i = 0; i < 256; i++) { 32 | if (other.keys[i] !== 0) { 33 | this.children[i] = other.children[other.keys[i] - 1] 34 | const child = this.children[i] 35 | if (child) { 36 | child.refcount++ 37 | } 38 | } 39 | } 40 | } 41 | 42 | if (other instanceof ArtNode256) { 43 | for (let i = 0; i < 256; i++) { 44 | this.children[i] = other.children[i] 45 | const child = this.children[i] 46 | if (child) { 47 | child.refcount++ 48 | } 49 | } 50 | ArtNode256.count++ 51 | } 52 | } 53 | 54 | public clone(): PartNode { 55 | return new ArtNode256(this) 56 | } 57 | 58 | public findChild(c: number): ChildPtr | null { 59 | if (this.children[c]) { 60 | return new ArrayChildPtr(this.children, c) 61 | } 62 | return null 63 | } 64 | 65 | public minimum(): Leaf | null { 66 | let idx = 0 67 | while (!this.children[idx]) { 68 | idx++ 69 | } 70 | return PartNode.minimum(this.children[idx]) 71 | } 72 | 73 | public addChild(ref: ChildPtr, c: number, child: PartNode): void { 74 | console.assert(this.refcount <= 4) 75 | 76 | this.numChildren++ 77 | this.children[c] = child 78 | child.refcount++ 79 | } 80 | 81 | public removeChild(ref: ChildPtr, c: number): void { 82 | console.assert(this.refcount <= 1) 83 | 84 | const child = this.children[c] 85 | 86 | if (child) { 87 | child.decrementRefcount() 88 | } 89 | 90 | this.children[c] = null 91 | this.numChildren-- 92 | 93 | if (this.numChildren === 37) { 94 | const result = new ArtNode48(this) 95 | ref.change(result) 96 | } 97 | } 98 | 99 | public exhausted(c: number): boolean { 100 | for (let i = c; i < 256; i++) { 101 | if (this.children[i]) { 102 | return false 103 | } 104 | } 105 | return true 106 | } 107 | 108 | public nextChildAtOrAfter(c: number): number { 109 | let pos = c 110 | for (; pos < 256; pos++) { 111 | if (this.children[pos]) { 112 | break 113 | } 114 | } 115 | return pos 116 | } 117 | 118 | public childAt(pos: number): PartNode | null { 119 | return this.children[pos] 120 | } 121 | 122 | public decrementRefcount(): number { 123 | if (--this.refcount <= 0) { 124 | let freed = 0 125 | for (let i = 0; i < 256; i++) { 126 | const child = this.children[i] 127 | if (child) { 128 | freed += child.decrementRefcount() 129 | } 130 | } 131 | ArtNode256.count-- 132 | return freed + 2120 133 | } 134 | return 0 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/ArtNode4.ts: -------------------------------------------------------------------------------- 1 | import { ArtNode } from "./ArtNode" 2 | import { ArtNode16 } from "./ArtNode16" 3 | import { PartNode } from "./PartNode" 4 | import { ChildPtr } from "./ChildPtr" 5 | import { ArrayChildPtr } from "./ArrayChildPtr" 6 | import { Leaf } from "./Leaf" 7 | import { arrayCopy } from "./utils" 8 | 9 | export class ArtNode4 extends ArtNode { 10 | public static count = 0 11 | 12 | keys: number[] = new Array(4).fill(0) 13 | children: (PartNode | null)[] = new Array(4).fill(null) 14 | 15 | constructor(other?: ArtNode4 | ArtNode16) { 16 | super(other) 17 | 18 | if (other instanceof ArtNode4) { 19 | this.keys = arrayCopy(other.keys, 0, this.keys, 0, other.numChildren) 20 | 21 | for (let i = 0; i < other.numChildren; i++) { 22 | this.children[i] = other.children[i] 23 | const child = this.children[i] 24 | if (child) { 25 | child.refcount++ 26 | } 27 | } 28 | ArtNode4.count++ 29 | } 30 | 31 | if (other instanceof ArtNode16) { 32 | ArtNode4.count++ 33 | 34 | console.assert(other.numChildren <= 4) 35 | 36 | this.numChildren = other.numChildren 37 | this.partialLen = other.partialLen 38 | 39 | this.partial = arrayCopy( 40 | other.partial, 41 | 0, 42 | this.partial, 43 | 0, 44 | Math.min(PartNode.MAX_PREFIX_LEN, this.partialLen), 45 | ) 46 | 47 | this.keys = arrayCopy(other.keys, 0, this.keys, 0, this.numChildren) 48 | 49 | for (let i = 0; i < this.numChildren; i++) { 50 | this.children[i] = other.children[i] 51 | const child = this.children[i] 52 | console.assert(child) 53 | if (child) { 54 | child.refcount++ 55 | } 56 | } 57 | } 58 | } 59 | 60 | public clone(): PartNode { 61 | return new ArtNode4(this) 62 | } 63 | 64 | public findChild(c: number): ChildPtr | null { 65 | for (let i = 0; i < this.numChildren; i++) { 66 | if (this.keys[i] === c) { 67 | return new ArrayChildPtr(this.children, i) 68 | } 69 | } 70 | return null 71 | } 72 | 73 | public minimum(): Leaf | null { 74 | return PartNode.minimum(this.children[0]) 75 | } 76 | 77 | public addChild(ref: ChildPtr, c: number, child: PartNode): void { 78 | console.assert(this.refcount <= 1) 79 | 80 | if (this.numChildren < 4) { 81 | let idx: number 82 | for (idx = 0; idx < this.numChildren; idx++) { 83 | if (c < this.keys[idx]) { 84 | break 85 | } 86 | } 87 | 88 | this.keys = arrayCopy( 89 | this.keys, 90 | idx, 91 | this.keys, 92 | idx + 1, 93 | this.numChildren - idx, 94 | ) 95 | 96 | this.children = arrayCopy( 97 | this.children, 98 | idx, 99 | this.children, 100 | idx + 1, 101 | this.numChildren - idx, 102 | ) 103 | 104 | this.keys[idx] = c 105 | this.children[idx] = child 106 | child.refcount++ 107 | this.numChildren++ 108 | } else { 109 | const result = new ArtNode16(this) 110 | ref.change(result) 111 | result.addChild(ref, c, child) 112 | } 113 | } 114 | 115 | public removeChild(ref: ChildPtr, c: number): void { 116 | let idx: number 117 | for (idx = 0; idx < this.numChildren; idx++) { 118 | if (c === this.keys[idx]) { 119 | break 120 | } 121 | } 122 | if (idx === this.numChildren) { 123 | return 124 | } 125 | 126 | const node = this.children[idx] 127 | if (node) { 128 | node.decrementRefcount() 129 | } 130 | 131 | this.keys = arrayCopy( 132 | this.keys, 133 | idx + 1, 134 | this.keys, 135 | idx, 136 | this.numChildren - idx - 1, 137 | ) 138 | 139 | this.children = arrayCopy( 140 | this.children, 141 | idx + 1, 142 | this.children, 143 | idx, 144 | this.numChildren - idx - 1, 145 | ) 146 | 147 | this.numChildren-- 148 | 149 | if (this.numChildren === 1) { 150 | let child = this.children[0] 151 | 152 | if (child instanceof Leaf === false) { 153 | if (child && child.refcount > 1) { 154 | child = child.clone() 155 | } 156 | 157 | const anChild = child 158 | let prefix = this.partialLen 159 | 160 | if (prefix < PartNode.MAX_PREFIX_LEN) { 161 | this.partial[prefix] = this.keys[0] 162 | prefix++ 163 | } 164 | 165 | if (prefix < PartNode.MAX_PREFIX_LEN) { 166 | const subPrefix = Math.min( 167 | anChild.partialLen, 168 | PartNode.MAX_PREFIX_LEN - prefix, 169 | ) 170 | 171 | this.partial = arrayCopy( 172 | anChild.partial, 173 | 0, 174 | this.partial, 175 | prefix, 176 | subPrefix, 177 | ) 178 | 179 | prefix += subPrefix 180 | } 181 | 182 | anChild.partial = arrayCopy( 183 | this.partial, 184 | 0, 185 | anChild.partial, 186 | 0, 187 | Math.min(prefix, PartNode.MAX_PREFIX_LEN), 188 | ) 189 | 190 | anChild.partialLen += this.partialLen + 1 191 | } 192 | if (child) { 193 | ref.change(child) 194 | } 195 | } 196 | } 197 | 198 | public exhausted(i: number): boolean { 199 | return i >= this.numChildren 200 | } 201 | 202 | public nextChildAtOrAfter(i: number): number { 203 | return i 204 | } 205 | 206 | public childAt(i: number): PartNode | null { 207 | return this.children[i] 208 | } 209 | 210 | public decrementRefcount(): number { 211 | if (--this.refcount <= 0) { 212 | let freed = 0 213 | for (let i = 0; i < this.numChildren; i++) { 214 | const child = this.children[i] 215 | if (child) { 216 | freed += child.decrementRefcount() 217 | } 218 | } 219 | ArtNode4.count-- 220 | return freed + 128 221 | } 222 | return 0 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/ArtNode48.ts: -------------------------------------------------------------------------------- 1 | import { Leaf } from "./Leaf" 2 | import { ArtNode } from "./ArtNode" 3 | import { ArtNode16 } from "./ArtNode16" 4 | import { ArtNode256 } from "./ArtNode256" 5 | import { PartNode } from "./PartNode" 6 | import { ChildPtr } from "./ChildPtr" 7 | import { ArrayChildPtr } from "./ArrayChildPtr" 8 | import { arrayCopy } from "./utils" 9 | 10 | export class ArtNode48 extends ArtNode { 11 | public static count = 0 12 | 13 | keys: number[] = new Array(256).fill(0) 14 | children: (PartNode | null)[] = new Array(48).fill(null) 15 | 16 | constructor(other?: ArtNode16 | ArtNode48 | ArtNode256) { 17 | super(other) 18 | 19 | if (other instanceof ArtNode16) { 20 | ArtNode48.count++ 21 | 22 | this.numChildren = other.numChildren 23 | this.partialLen = other.partialLen 24 | 25 | this.partial = arrayCopy( 26 | other.partial, 27 | 0, 28 | this.partial, 29 | 0, 30 | Math.min(PartNode.MAX_PREFIX_LEN, this.partialLen), 31 | ) 32 | 33 | for (let i = 0; i < other.numChildren; i++) { 34 | this.keys[other.keys[i]] = i + 1 35 | this.children[i] = other.children[i] 36 | 37 | const child = this.children[i] 38 | if (child) { 39 | child.refcount++ 40 | } 41 | } 42 | } 43 | 44 | if (other instanceof ArtNode48) { 45 | this.keys = arrayCopy(other.keys, 0, this.keys, 0, 256) 46 | for (let i = 0; i < 48; i++) { 47 | this.children[i] = other.children[i] 48 | const child = this.children[i] 49 | if (child) { 50 | child.refcount++ 51 | } 52 | } 53 | ArtNode48.count++ 54 | } 55 | 56 | if (other instanceof ArtNode256) { 57 | ArtNode48.count++ 58 | 59 | console.assert(other.numChildren <= 48) 60 | 61 | this.numChildren = other.numChildren 62 | this.partialLen = other.partialLen 63 | 64 | this.partial = arrayCopy( 65 | other.partial, 66 | 0, 67 | this.partial, 68 | 0, 69 | Math.min(PartNode.MAX_PREFIX_LEN, this.partialLen), 70 | ) 71 | 72 | let pos = 0 73 | for (let i = 0; i < 256; i++) { 74 | const child = other.children[i] 75 | if (child) { 76 | this.keys[i] = pos + 1 77 | this.children[pos] = other.children[i] 78 | const child = this.children[pos] 79 | if (child) { 80 | child.refcount++ 81 | } 82 | pos++ 83 | } 84 | } 85 | } 86 | } 87 | 88 | public clone(): PartNode { 89 | return new ArtNode48(this) 90 | } 91 | 92 | public findChild(c: number): ChildPtr | null { 93 | let idx = this.keys[c] 94 | 95 | if (idx !== 0) { 96 | return new ArrayChildPtr(this.children, idx - 1) 97 | } 98 | return null 99 | } 100 | 101 | public minimum(): Leaf | null { 102 | let idx = 0 103 | while (this.keys[idx] === 0) { 104 | idx++ 105 | } 106 | return PartNode.minimum(this.children[this.keys[idx] - 1]) 107 | } 108 | 109 | public addChild(ref: ChildPtr, c: number, child: PartNode): void { 110 | console.assert(this.refcount <= 1) 111 | 112 | if (this.numChildren < 48) { 113 | let pos = 0 114 | 115 | while (this.children[pos]) { 116 | pos++ 117 | } 118 | 119 | this.children[pos] = child 120 | child.refcount++ 121 | this.keys[c] = pos + 1 122 | this.numChildren++ 123 | } else { 124 | const result = new ArtNode256(this) 125 | ref.change(result) 126 | result.addChild(ref, c, child) 127 | } 128 | } 129 | 130 | public removeChild(ref: ChildPtr, c: number): void { 131 | const pos = this.keys[c] 132 | this.keys[c] = 0 133 | const child = this.children[pos - 1] 134 | if (child) { 135 | child.decrementRefcount() 136 | } 137 | this.children[pos - 1] = null 138 | this.numChildren-- 139 | 140 | if (this.numChildren === 12) { 141 | const result = new ArtNode16(this) 142 | ref.change(result) 143 | } 144 | } 145 | 146 | public exhausted(c: number): boolean { 147 | for (let i = c; i < 256; i++) { 148 | if (this.keys[i] !== 0) { 149 | return false 150 | } 151 | } 152 | return true 153 | } 154 | 155 | public nextChildAtOrAfter(c: number): number { 156 | let pos = c 157 | for (; pos < 256; pos++) { 158 | if (this.keys[pos] !== 0) { 159 | break 160 | } 161 | } 162 | return pos 163 | } 164 | 165 | public childAt(i: number): PartNode | null { 166 | return this.children[this.keys[i] - 1] 167 | } 168 | 169 | public decrementRefcount(): number { 170 | if (--this.refcount <= 0) { 171 | let freed = 0 172 | for (let i = 0; i < this.numChildren; i++) { 173 | const child = this.children[i] 174 | if (child) { 175 | freed += child.decrementRefcount() 176 | } 177 | } 178 | ArtNode48.count-- 179 | return freed + 728 180 | } 181 | return 0 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/ArtTree.ts: -------------------------------------------------------------------------------- 1 | import { ChildPtr } from "./ChildPtr" 2 | import { PartNode } from "./PartNode" 3 | import { Leaf } from "./Leaf" 4 | import { ArtNode } from "./ArtNode" 5 | import { ArtIterator } from "./ArtIterator" 6 | 7 | export class ArtTree extends ChildPtr { 8 | root: PartNode | null = null 9 | numElements = 0 10 | 11 | constructor(other?: ArtTree) { 12 | super() 13 | if (other) { 14 | this.root = other.root 15 | this.numElements = other.numElements 16 | } 17 | } 18 | 19 | public snapshot(): ArtTree { 20 | const b = new ArtTree() 21 | 22 | if (this.root) { 23 | b.root = PartNode.clone(this.root) 24 | if (b.root) { 25 | b.root.refcount++ 26 | } 27 | } 28 | 29 | b.numElements = this.numElements 30 | return b 31 | } 32 | 33 | get(): PartNode | null { 34 | return this.root 35 | } 36 | 37 | set(n: PartNode): void { 38 | this.root = n 39 | } 40 | 41 | public search(key: number[]): object | null { 42 | let n = this.root 43 | let prefixLen 44 | let depth = 0 45 | 46 | while (n) { 47 | if (n instanceof Leaf) { 48 | const l = n 49 | if (l.matches(key)) { 50 | return l.value 51 | } else { 52 | return null 53 | } 54 | } else { 55 | const an = n 56 | if (an.partialLen > 0) { 57 | prefixLen = an.checkPrefix(key, depth) 58 | if (prefixLen !== Math.min(PartNode.MAX_PREFIX_LEN, an.partialLen)) { 59 | return null 60 | } 61 | depth += an.partialLen 62 | } 63 | 64 | if (depth >= key.length) { 65 | return null 66 | } 67 | 68 | const child = an.findChild(key[depth]) 69 | n = child ? child.get() : null 70 | depth++ 71 | } 72 | } 73 | return null 74 | } 75 | 76 | public insert(key: number[], value: object): void { 77 | if (PartNode.insert(this.root, this, key, value, 0, false)) { 78 | this.numElements++ 79 | } 80 | } 81 | 82 | public delete(key: number[]): void { 83 | if (this.root) { 84 | const childIsLeaf = this.root instanceof Leaf 85 | const doDelete = this.root.delete(this, key, 0, false) 86 | if (doDelete) { 87 | this.numElements-- 88 | if (childIsLeaf) { 89 | this.root = null 90 | } 91 | } 92 | } 93 | } 94 | 95 | public interator(): Iterator<[number[], object]> { 96 | return new ArtIterator(this.root) 97 | } 98 | 99 | public prefixIterator(prefix: number[]): Iterator<[number[], object]> { 100 | let n = this.root 101 | let prefixLen 102 | let depth = 0 103 | 104 | while (n) { 105 | if (n instanceof Leaf) { 106 | const l = n 107 | 108 | if (l.prefixMatches(prefix)) { 109 | return new ArtIterator(l) 110 | } else { 111 | return new ArtIterator(null) 112 | } 113 | } else { 114 | if (depth === prefix.length) { 115 | const min = n.minimum() 116 | if (min && min.prefixMatches(prefix)) { 117 | return new ArtIterator(n) 118 | } else { 119 | return new ArtIterator(null) 120 | } 121 | } else { 122 | const an = n 123 | 124 | if (an.partialLen > 0) { 125 | prefixLen = an.prefixMismatch(prefix, depth) 126 | if (prefixLen === 0) { 127 | return new ArtIterator(null) 128 | } else if (depth + prefixLen === prefix.length) { 129 | return new ArtIterator(n) 130 | } else { 131 | depth += an.partialLen 132 | } 133 | } 134 | } 135 | } 136 | } 137 | return new ArtIterator(null) 138 | } 139 | 140 | public get size(): number { 141 | return this.numElements 142 | } 143 | 144 | public destroy(): number { 145 | if (this.root) { 146 | const result = this.root.decrementRefcount() 147 | this.root = null 148 | return result 149 | } else { 150 | return 0 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/ChildPtr.ts: -------------------------------------------------------------------------------- 1 | import { PartNode } from "./PartNode" 2 | 3 | export abstract class ChildPtr { 4 | abstract get(): PartNode | null 5 | abstract set(n: PartNode): void 6 | change(n: PartNode): void { 7 | n.refcount++ 8 | const node = this.get() 9 | if (node) { 10 | node.decrementRefcount() 11 | } 12 | this.set(n) 13 | } 14 | changeNoDecrement(n: PartNode): void { 15 | n.refcount++ 16 | this.set(n) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Deque.ts: -------------------------------------------------------------------------------- 1 | export class Deque { 2 | private coll: T[] = [] 3 | push(value: T): void { 4 | this.coll.push(value) 5 | } 6 | peek(): T | null { 7 | return this.coll.length > 0 ? this.coll[this.coll.length - 1] : null 8 | } 9 | pop(): T { 10 | const ret = this.coll.pop() 11 | if (ret !== undefined) { 12 | return ret 13 | } else { 14 | throw new Error("No such element") 15 | } 16 | } 17 | isEmpty(): boolean { 18 | return this.coll.length === 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/IterCallback.ts: -------------------------------------------------------------------------------- 1 | export interface IterCallback { 2 | apply(key: number[], value: object): void 3 | } 4 | -------------------------------------------------------------------------------- /src/Leaf.ts: -------------------------------------------------------------------------------- 1 | import { ChildPtr } from "./ChildPtr" 2 | import { PartNode } from "./PartNode" 3 | import { ArtNode4 } from "./ArtNode4" 4 | import { arrayCopy } from "./utils" 5 | 6 | export class Leaf extends PartNode { 7 | public static count = 0 8 | 9 | key: number[] 10 | value: object 11 | 12 | constructor(key: number[], value: object) { 13 | super() 14 | this.key = key 15 | this.value = value 16 | Leaf.count++ 17 | } 18 | 19 | public clone(): PartNode { 20 | return new Leaf(this.key, this.value) 21 | } 22 | 23 | public matches(key: number[]): boolean { 24 | if (this.key.length !== key.length) { 25 | return false 26 | } 27 | for (let i = 0; i < key.length; i++) { 28 | if (this.key[i] !== key[i]) { 29 | return false 30 | } 31 | } 32 | return true 33 | } 34 | 35 | public prefixMatches(prefix: number[]): boolean { 36 | if (this.key.length < prefix.length) { 37 | return false 38 | } 39 | for (let i = 0; i < prefix.length; i++) { 40 | if (this.key[i] !== prefix[i]) { 41 | return false 42 | } 43 | } 44 | return true 45 | } 46 | 47 | public minimum(): Leaf { 48 | return this 49 | } 50 | 51 | public longestCommonPrefix(other: Leaf, depth: number): number { 52 | const maxCmp: number = Math.min(this.key.length, other.key.length) - depth 53 | let idx: number 54 | for (idx = 0; idx < maxCmp; idx++) { 55 | if (this.key[depth + idx] !== other.key[depth + idx]) { 56 | return idx 57 | } 58 | } 59 | return idx 60 | } 61 | 62 | public insert( 63 | ref: ChildPtr, 64 | key: number[], 65 | value: object, 66 | depth: number, 67 | forceClone: boolean, 68 | ): boolean { 69 | const clone = forceClone || this.refcount > 1 70 | 71 | if (this.matches(key)) { 72 | if (clone) { 73 | ref.change(new Leaf(key, value)) 74 | } else { 75 | this.value = value 76 | } 77 | return false 78 | } else { 79 | const l2 = new Leaf(key, value) 80 | const longestPrefix = this.longestCommonPrefix(l2, depth) 81 | 82 | if ( 83 | depth + longestPrefix >= this.key.length || 84 | depth + longestPrefix >= key.length 85 | ) { 86 | throw new Error( 87 | `Keys cannot be prefixes of other keys: [${key.join( 88 | ", ", 89 | )}] in [${this.key.join(", ")}]`, 90 | ) 91 | } 92 | 93 | const result = new ArtNode4() 94 | result.partialLen = longestPrefix 95 | 96 | const refOld = ref.get() 97 | ref.changeNoDecrement(result) 98 | 99 | result.partial = arrayCopy( 100 | key, 101 | depth, 102 | result.partial, 103 | 0, 104 | Math.min(PartNode.MAX_PREFIX_LEN, longestPrefix), 105 | ) 106 | 107 | result.addChild(ref, this.key[depth + longestPrefix], this) 108 | result.addChild(ref, l2.key[depth + longestPrefix], l2) 109 | 110 | if (refOld) { 111 | refOld.decrementRefcount() 112 | } 113 | 114 | return true 115 | } 116 | } 117 | 118 | public delete( 119 | ref: ChildPtr, 120 | key: number[], 121 | depth: number, 122 | forceClone: boolean, 123 | ): boolean { 124 | return this.matches(key) 125 | } 126 | 127 | public exhausted(i: number): boolean { 128 | return i > 0 129 | } 130 | 131 | public decrementRefcount(): number { 132 | if (--this.refcount <= 0) { 133 | Leaf.count-- 134 | return 32 135 | } 136 | return 0 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/PartNode.ts: -------------------------------------------------------------------------------- 1 | import { ChildPtr } from "./ChildPtr" 2 | import { Leaf } from "./Leaf" 3 | 4 | export abstract class PartNode { 5 | refcount = 0 6 | 7 | static MAX_PREFIX_LEN = 8 8 | 9 | public abstract clone(): PartNode 10 | public static clone(n: PartNode | null): PartNode | null { 11 | return n ? n.clone() : null 12 | } 13 | 14 | public abstract minimum(): Leaf | null 15 | public static minimum(n: PartNode | null): Leaf | null { 16 | return n ? n.minimum() : null 17 | } 18 | 19 | public abstract insert( 20 | ref: ChildPtr, 21 | key: number[], 22 | value: object, 23 | depth: number, 24 | forceClone: boolean, 25 | ): boolean 26 | public static insert( 27 | n: PartNode | null, 28 | ref: ChildPtr, 29 | key: number[], 30 | value: object, 31 | depth: number, 32 | forceClone: boolean, 33 | ): boolean { 34 | if (n) { 35 | return n.insert(ref, key, value, depth, forceClone) 36 | } else { 37 | ref.change(new Leaf(key, value)) 38 | return true 39 | } 40 | } 41 | 42 | public abstract delete( 43 | ref: ChildPtr, 44 | key: number[], 45 | depth: number, 46 | forceClone: boolean, 47 | ): boolean 48 | 49 | public abstract decrementRefcount(): number 50 | 51 | public abstract exhausted(i: number | null): boolean 52 | public static exhausted(n: PartNode | null, i: number): boolean { 53 | return n ? n.exhausted(i) : true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Leaf } from "./Leaf" 2 | import { ArtTree } from "./ArtTree" 3 | 4 | export { Leaf, ArtTree } 5 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export const arrayCopy = ( 2 | src: Array, 3 | srcPos: number, 4 | dest: Array, 5 | destPos: number, 6 | len: number, 7 | ): Array => { 8 | return dest 9 | .slice(0, destPos) 10 | .concat( 11 | src.slice(srcPos, srcPos + len), 12 | dest.slice(destPos + len, dest.length), 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /test/ArtTree.test.js: -------------------------------------------------------------------------------- 1 | const { ArtTree } = require("../out/index") 2 | 3 | const maxN = 10000 4 | const maxKeyLen = 10 5 | 6 | const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min 7 | const range = n => new Array(n).fill(n) 8 | 9 | const genKeys = () => { 10 | let seq = range(maxN) 11 | .map(() => 12 | range(randInt(5, maxKeyLen)) 13 | .map(() => randInt(0, 255) + 1) 14 | .concat(0), 15 | ) 16 | .map(s => s.join(",")) 17 | seq = new Set(seq) 18 | return Array.from(seq).map(s => s.split(/,/g).map(s => +s)) 19 | } 20 | 21 | test("create, destroy", () => { 22 | const t = new ArtTree() 23 | expect(t.size).toBe(0) 24 | t.destroy() 25 | expect(t.size).toBe(0) 26 | }) 27 | 28 | test("insert, search", () => { 29 | const t = new ArtTree() 30 | const keys = genKeys() 31 | const holdOut = 10 32 | 33 | for (let i = 0; i < keys.length - holdOut; i++) { 34 | const k = keys[i] 35 | expect(t.search(k)).toBe(null) 36 | t.insert(k, i) 37 | expect(t.search(k)).toBe(i) 38 | } 39 | 40 | expect(t.size).toBe(keys.length - holdOut) 41 | 42 | for (let i = keys.length - holdOut; i < keys.length; i++) { 43 | expect(t.search(keys[i])).toBe(null) 44 | } 45 | }) 46 | 47 | test("insert, delete", () => { 48 | const t = new ArtTree() 49 | const keys = genKeys() 50 | 51 | for (let i = 0; i < keys.length; i++) { 52 | const k = keys[i] 53 | expect(t.search(k)).toBe(null) 54 | t.insert(k, i) 55 | expect(t.search(k)).toBe(i) 56 | } 57 | 58 | expect(t.size).toBe(keys.length) 59 | 60 | for (let i = 0; i < keys.length; i++) { 61 | const k = keys[i] 62 | expect(t.search(k)).toBe(i) 63 | t.delete(k) 64 | expect(t.search(k)).toBe(null) 65 | } 66 | 67 | expect(t.size).toBe(0) 68 | }) 69 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/*"], 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "target": 6 | "ES2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, 7 | "module": 8 | "es2015" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 9 | "lib": [ 10 | "es2015", 11 | "es2015.iterable" 12 | ] /* Specify library files to be included in the compilation: */, 13 | // "allowJs": true /* Allow javascript files to be compiled. */, 14 | // "checkJs": true /* Report errors in .js files. */, 15 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 16 | // "declaration": true /* Generates corresponding '.d.ts' file. */, 17 | // "sourceMap": true /* Generates corresponding '.map' file. */, 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | "outDir": "./out" /* Redirect output structure to the directory. */, 20 | // "rootDir": 21 | // "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 22 | "removeComments": true /* Do not emit comments to output. */, 23 | // "noEmit": true /* Do not emit outputs. */, 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true /* Enable all strict type-checking options. */, 30 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 31 | "strictNullChecks": true /* Enable strict null checks. */, 32 | "strictFunctionTypes": true /* Enable strict checking of function types. */, 33 | "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 34 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 35 | 36 | /* Additional Checks */ 37 | // "noUnusedLocals": true /* Report errors on unused locals. */, 38 | // "noUnusedParameters": true /* Report errors on unused parameters. */, 39 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 40 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ 41 | 42 | /* Module Resolution Options */ 43 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 44 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 47 | // "typeRoots": [], /* List of folders to include type definitions from. */ 48 | // "types": [], /* Type declaration files to be included in compilation. */ 49 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | } 62 | } 63 | --------------------------------------------------------------------------------