├── .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 |
--------------------------------------------------------------------------------