@blackglory/structures 0.13.2 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -12
- package/lib/emitter.d.ts +5 -4
- package/lib/emitter.js +4 -0
- package/lib/emitter.js.map +1 -1
- package/package.json +3 -2
- package/src/array.ts +19 -0
- package/src/big-map.ts +98 -0
- package/src/big-set.ts +72 -0
- package/src/bit-set.ts +124 -0
- package/src/box.ts +15 -0
- package/src/cons.ts +15 -0
- package/src/dynamic-typed-array.ts +201 -0
- package/src/emitter.ts +76 -0
- package/src/expirable-map.ts +122 -0
- package/src/hash-map.ts +34 -0
- package/src/hash-set.ts +38 -0
- package/src/index.ts +24 -0
- package/src/lru-map.ts +69 -0
- package/src/queue.ts +30 -0
- package/src/radix-tree.ts +264 -0
- package/src/sorted-set.ts +34 -0
- package/src/sparse-map.ts +83 -0
- package/src/sparse-set.ts +67 -0
- package/src/string-radix-tree.ts +263 -0
- package/src/string-trie-map.ts +102 -0
- package/src/tlru-map.ts +162 -0
- package/src/trie-map.ts +102 -0
- package/src/typed-bit-set.ts +129 -0
- package/src/typed-sparse-map.ts +97 -0
- package/src/typed-sparse-set.ts +71 -0
- package/src/utils/skip-list.ts +195 -0
- package/src/utils/trailing-zeros.ts +15 -0
package/src/emitter.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { go } from '@blackglory/go'
|
|
2
|
+
|
|
3
|
+
type Listener<Args extends unknown[]> = (...args: Args) => void
|
|
4
|
+
|
|
5
|
+
export class Emitter<
|
|
6
|
+
EventToArgs extends Record<Event, unknown[]> = Record<
|
|
7
|
+
string | number | symbol
|
|
8
|
+
, unknown[]
|
|
9
|
+
>
|
|
10
|
+
, Event extends string | number | symbol = keyof EventToArgs
|
|
11
|
+
> {
|
|
12
|
+
private map = new Map<
|
|
13
|
+
Event
|
|
14
|
+
, Set<Listener<EventToArgs[Event]>>
|
|
15
|
+
>()
|
|
16
|
+
|
|
17
|
+
get [Symbol.toStringTag](): string {
|
|
18
|
+
return this.constructor.name
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
on<T extends Event>(
|
|
22
|
+
event: T
|
|
23
|
+
, listener: Listener<EventToArgs[T]>
|
|
24
|
+
): () => void {
|
|
25
|
+
const set = go(() => {
|
|
26
|
+
const set = this.map.get(event)
|
|
27
|
+
if (set) {
|
|
28
|
+
return set
|
|
29
|
+
} else {
|
|
30
|
+
const set = new Set<Listener<EventToArgs[keyof EventToArgs]>>()
|
|
31
|
+
this.map.set(event, set)
|
|
32
|
+
return set
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
set.add(handler as Listener<EventToArgs[Event]>)
|
|
36
|
+
|
|
37
|
+
return () => {
|
|
38
|
+
const handlers = this.map.get(event)
|
|
39
|
+
if (handlers) {
|
|
40
|
+
const deleted = handlers.delete(
|
|
41
|
+
handler as Listener<EventToArgs[Event]>
|
|
42
|
+
)
|
|
43
|
+
if (deleted && handlers.size === 0) {
|
|
44
|
+
this.map.delete(event)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function handler(...args: Parameters<Listener<EventToArgs[T]>>): void {
|
|
50
|
+
listener(...args)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
once<T extends Event>(
|
|
55
|
+
event: T
|
|
56
|
+
, listener: Listener<EventToArgs[T]>
|
|
57
|
+
): () => void {
|
|
58
|
+
const removeListener = this.on(
|
|
59
|
+
event
|
|
60
|
+
, (...args: Parameters<Listener<EventToArgs[T]>>) => {
|
|
61
|
+
listener(...args)
|
|
62
|
+
removeListener()
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return removeListener
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
emit<T extends Event>(event: T, ...args: EventToArgs[T]): void {
|
|
70
|
+
this.map.get(event)?.forEach(cb => cb(...args))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
removeAllListeners<T extends Event>(event: T): void {
|
|
74
|
+
this.map.get(event)?.clear()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { setSchedule } from 'extra-timers'
|
|
2
|
+
|
|
3
|
+
export class ExpirableMap<K, V> {
|
|
4
|
+
private map = new Map<K, V>()
|
|
5
|
+
private cancelScheduledCleaner?: () => void
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 按过期时间升序排列所有项目的元数据
|
|
9
|
+
*/
|
|
10
|
+
protected itemMetadataSortedByExpirationTime: Array<{
|
|
11
|
+
key: K
|
|
12
|
+
expirationTime: number
|
|
13
|
+
}> = []
|
|
14
|
+
|
|
15
|
+
get [Symbol.toStringTag](): string {
|
|
16
|
+
return this.constructor.name
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get size(): number {
|
|
20
|
+
return this.map.size
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set(key: K, value: V, timeToLive: number = Infinity): this {
|
|
24
|
+
this.map.set(key, value)
|
|
25
|
+
|
|
26
|
+
this.removeItemMetadata(key)
|
|
27
|
+
this.addItemMetadata(key, Date.now() + timeToLive)
|
|
28
|
+
|
|
29
|
+
return this
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
has(key: K): boolean {
|
|
33
|
+
return this.map.has(key)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get(key: K): V | undefined {
|
|
37
|
+
return this.map.get(key)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
delete(key: K): boolean {
|
|
41
|
+
const exists = this.map.delete(key)
|
|
42
|
+
if (exists) {
|
|
43
|
+
const index = this.itemMetadataSortedByExpirationTime.findIndex(x => x.key === key)
|
|
44
|
+
this.itemMetadataSortedByExpirationTime.splice(index, 1)
|
|
45
|
+
|
|
46
|
+
// 如果被删除的项目是第一个项目, 则需要重新规划过期清理器
|
|
47
|
+
if (index === 0) {
|
|
48
|
+
this.cancelScheduledCleaner?.()
|
|
49
|
+
this.scheduleCleaner()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return exists
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
clear(): void {
|
|
56
|
+
this.map.clear()
|
|
57
|
+
this.cancelScheduledCleaner?.()
|
|
58
|
+
this.itemMetadataSortedByExpirationTime = []
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private addItemMetadata(key: K, expirationTime: number): void {
|
|
62
|
+
for (let i = 0; i < this.itemMetadataSortedByExpirationTime.length; i++) {
|
|
63
|
+
const item = this.itemMetadataSortedByExpirationTime[i]
|
|
64
|
+
if (expirationTime < item.expirationTime) {
|
|
65
|
+
this.itemMetadataSortedByExpirationTime.splice(i, 0, { key, expirationTime })
|
|
66
|
+
|
|
67
|
+
// 如果本次插入导致原先的第一个项目不再是第一个项目, 则需要重新规划过期清理器
|
|
68
|
+
if (i === 0) {
|
|
69
|
+
this.cancelScheduledCleaner?.()
|
|
70
|
+
this.scheduleCleaner()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 如果代码运行到此处, 意味着数组要么为空, 要么新项目过期时间大于数组内的所有项目, 故直接插入到数组尾端.
|
|
78
|
+
this.itemMetadataSortedByExpirationTime.push({ key, expirationTime })
|
|
79
|
+
// 如果新插入的项目是唯一而项目, 则需要重新规划过期清理器
|
|
80
|
+
if (this.itemMetadataSortedByExpirationTime.length === 1) {
|
|
81
|
+
this.cancelScheduledCleaner?.()
|
|
82
|
+
this.scheduleCleaner()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private removeItemMetadata(key: K): void {
|
|
87
|
+
const index = this.itemMetadataSortedByExpirationTime.findIndex(x => x.key === key)
|
|
88
|
+
if (index >= 0) {
|
|
89
|
+
this.itemMetadataSortedByExpirationTime.splice(index, 1)
|
|
90
|
+
|
|
91
|
+
// 如果被移除的是第一个项目, 则需要重新规划过期清理器
|
|
92
|
+
if (index === 0) {
|
|
93
|
+
this.cancelScheduledCleaner?.()
|
|
94
|
+
this.scheduleCleaner()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private scheduleCleaner(): void {
|
|
100
|
+
if (this.itemMetadataSortedByExpirationTime.length > 0) {
|
|
101
|
+
const item = this.itemMetadataSortedByExpirationTime[0]
|
|
102
|
+
if (Number.isFinite(item.expirationTime)) {
|
|
103
|
+
this.cancelScheduledCleaner = setSchedule(item.expirationTime, () => {
|
|
104
|
+
this.clearExpiredItems(Date.now())
|
|
105
|
+
this.cancelScheduledCleaner?.()
|
|
106
|
+
this.scheduleCleaner()
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private clearExpiredItems(timestamp: number): void {
|
|
113
|
+
const indexOfFirstUnexpired = this.itemMetadataSortedByExpirationTime.findIndex(
|
|
114
|
+
x => x.expirationTime > timestamp
|
|
115
|
+
)
|
|
116
|
+
const expiredItemKeys =
|
|
117
|
+
indexOfFirstUnexpired >= 0
|
|
118
|
+
? this.itemMetadataSortedByExpirationTime.splice(0, indexOfFirstUnexpired)
|
|
119
|
+
: this.itemMetadataSortedByExpirationTime.splice(0, this.itemMetadataSortedByExpirationTime.length)
|
|
120
|
+
expiredItemKeys.forEach(x => this.map.delete(x.key))
|
|
121
|
+
}
|
|
122
|
+
}
|
package/src/hash-map.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class HashMap<K, V, Hash = unknown> {
|
|
2
|
+
private map = new Map<Hash, V>()
|
|
3
|
+
|
|
4
|
+
get [Symbol.toStringTag](): string {
|
|
5
|
+
return this.constructor.name
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get size(): number {
|
|
9
|
+
return this.map.size
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
constructor(private hash: (key: K) => Hash) {}
|
|
13
|
+
|
|
14
|
+
set(key: K, value: V): this {
|
|
15
|
+
this.map.set(this.hash(key), value)
|
|
16
|
+
return this
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
has(key: K): boolean {
|
|
20
|
+
return this.map.has(this.hash(key))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(key: K): V | undefined {
|
|
24
|
+
return this.map.get(this.hash(key))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
delete(key: K): boolean {
|
|
28
|
+
return this.map.delete(this.hash(key))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clear(): void {
|
|
32
|
+
this.map.clear()
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/hash-set.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class HashSet<V, Hash = unknown> implements Iterable<V> {
|
|
2
|
+
private map = new Map<Hash, V>()
|
|
3
|
+
|
|
4
|
+
get [Symbol.toStringTag](): string {
|
|
5
|
+
return this.constructor.name
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get size(): number {
|
|
9
|
+
return this.map.size
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
[Symbol.iterator](): IterableIterator<V> {
|
|
13
|
+
return this.map.values()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(private hash: (value: V) => Hash) {}
|
|
17
|
+
|
|
18
|
+
add(value: V): this {
|
|
19
|
+
this.map.set(this.hash(value), value)
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
delete(value: V): boolean {
|
|
24
|
+
return this.map.delete(this.hash(value))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
has(value: V): boolean {
|
|
28
|
+
return this.map.has(this.hash(value))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clear(): void {
|
|
32
|
+
this.map.clear()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
values(): IterableIterator<V> {
|
|
36
|
+
return this.map.values()
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export * from './box.js'
|
|
2
|
+
export * from './cons.js'
|
|
3
|
+
export * from './array.js'
|
|
4
|
+
export * from './emitter.js'
|
|
5
|
+
export * from './big-map.js'
|
|
6
|
+
export * from './big-set.js'
|
|
7
|
+
export * from './hash-map.js'
|
|
8
|
+
export * from './hash-set.js'
|
|
9
|
+
export * from './queue.js'
|
|
10
|
+
export * from './lru-map.js'
|
|
11
|
+
export * from './expirable-map.js'
|
|
12
|
+
export * from './tlru-map.js'
|
|
13
|
+
export * from './trie-map.js'
|
|
14
|
+
export * from './string-trie-map.js'
|
|
15
|
+
export { RadixTree } from './radix-tree.js'
|
|
16
|
+
export { StringRadixTree } from './string-radix-tree.js'
|
|
17
|
+
export * from './sparse-set.js'
|
|
18
|
+
export * from './sparse-map.js'
|
|
19
|
+
export * from './typed-sparse-map.js'
|
|
20
|
+
export * from './typed-sparse-set.js'
|
|
21
|
+
export { DynamicTypedArray } from './dynamic-typed-array.js'
|
|
22
|
+
export * from './sorted-set.js'
|
|
23
|
+
export * from './bit-set.js'
|
|
24
|
+
export * from './typed-bit-set.js'
|
package/src/lru-map.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { assert } from '@blackglory/errors'
|
|
2
|
+
import { first } from 'iterable-operator'
|
|
3
|
+
|
|
4
|
+
export class LRUMap<K, V> {
|
|
5
|
+
private limit: number
|
|
6
|
+
private map = new Map<K, V>()
|
|
7
|
+
|
|
8
|
+
get [Symbol.toStringTag](): string {
|
|
9
|
+
return this.constructor.name
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get size(): number {
|
|
13
|
+
return this.map.size
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(limit: number) {
|
|
17
|
+
assert(Number.isInteger(limit), 'The parameter limit must be an integer')
|
|
18
|
+
assert(limit > 0, 'The parameter limit must be a positive value')
|
|
19
|
+
|
|
20
|
+
this.limit = limit
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set(key: K, value: V): this {
|
|
24
|
+
if (this.map.has(key)) {
|
|
25
|
+
this.updateItem(key, value)
|
|
26
|
+
} else {
|
|
27
|
+
if (this.map.size === this.limit) {
|
|
28
|
+
this.map.delete(this.getColdestKey()!)
|
|
29
|
+
}
|
|
30
|
+
this.map.set(key, value)
|
|
31
|
+
}
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
has(key: K): boolean {
|
|
36
|
+
return this.map.has(key)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(key: K): V | undefined {
|
|
40
|
+
if (!this.has(key)) return undefined
|
|
41
|
+
|
|
42
|
+
const value = this.map.get(key)!
|
|
43
|
+
this.updateItem(key, value)
|
|
44
|
+
return value
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
delete(key: K): boolean {
|
|
48
|
+
return this.map.delete(key)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
clear(): void {
|
|
52
|
+
this.map.clear()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Reinsert the item.
|
|
57
|
+
*/
|
|
58
|
+
private updateItem(key: K, value: V): void {
|
|
59
|
+
this.map.delete(key)
|
|
60
|
+
this.map.set(key, value)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Return the earliest key inserted in the Map
|
|
65
|
+
*/
|
|
66
|
+
private getColdestKey(): K | undefined {
|
|
67
|
+
return first(this.map.keys())
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/queue.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class Queue<T> {
|
|
2
|
+
private items: T[] = []
|
|
3
|
+
|
|
4
|
+
get [Symbol.toStringTag](): string {
|
|
5
|
+
return this.constructor.name
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get size(): number {
|
|
9
|
+
return this.items.length
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
empty(): void {
|
|
13
|
+
this.items.length = 0
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
enqueue(...items: T[]): void {
|
|
17
|
+
this.items.push(...items)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
dequeue(): T | undefined {
|
|
21
|
+
return this.items.shift()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
remove(item: T): void {
|
|
25
|
+
let index: number
|
|
26
|
+
while ((index = this.items.indexOf(item)) >= 0) {
|
|
27
|
+
this.items.splice(index, 1)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { toArray, first, map, find } from 'iterable-operator'
|
|
2
|
+
import { isntUndefined } from 'extra-utils'
|
|
3
|
+
|
|
4
|
+
class TreeNode<T, U> {
|
|
5
|
+
children = new Map<T[], TreeNode<T, U>>()
|
|
6
|
+
|
|
7
|
+
constructor(public value?: U) {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class RadixTree<K extends Iterable<T>, V, T = unknown> {
|
|
11
|
+
private root = new TreeNode<T, V>()
|
|
12
|
+
|
|
13
|
+
get [Symbol.toStringTag](): string {
|
|
14
|
+
return this.constructor.name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
entries(): IterableIterator<[key: T[], value: V]> {
|
|
18
|
+
return dfs(this.root, [])
|
|
19
|
+
|
|
20
|
+
function* dfs(
|
|
21
|
+
node: TreeNode<T, V>
|
|
22
|
+
, path: T[]
|
|
23
|
+
): IterableIterator<[key: T[], value: V]> {
|
|
24
|
+
for (const [prefix, childNode] of node.children) {
|
|
25
|
+
const newPath = [...path, ...prefix]
|
|
26
|
+
if (isntUndefined(childNode.value)) {
|
|
27
|
+
yield [newPath, childNode.value]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
yield* dfs(childNode, newPath)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
keys(): IterableIterator<T[]> {
|
|
36
|
+
return map(this.entries(), ([key]) => key)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
values(): IterableIterator<V> {
|
|
40
|
+
return map(this.entries(), ([, value]) => value)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
set(key: K, value: V): this {
|
|
44
|
+
set(toArray(key), value, this.root)
|
|
45
|
+
return this
|
|
46
|
+
|
|
47
|
+
function set(key: T[], value: V, node: TreeNode<T, V>): void {
|
|
48
|
+
const commonPrefix = findCommonPrefix(node.children.keys(), key)
|
|
49
|
+
if (isntUndefined(commonPrefix)) {
|
|
50
|
+
const { prefix, commonPartLength } = commonPrefix
|
|
51
|
+
if (prefix.length === commonPartLength) {
|
|
52
|
+
// 现有节点的前缀整个都被使用的情况, 不需要在这一级创建新的前缀节点.
|
|
53
|
+
const prefixNode = node.children.get(prefix)!
|
|
54
|
+
|
|
55
|
+
if (key.length === prefix.length) {
|
|
56
|
+
// 前缀节点就是目标节点.
|
|
57
|
+
prefixNode.value = value
|
|
58
|
+
} else {
|
|
59
|
+
// 前缀节点不是目标节点
|
|
60
|
+
const newKey = key.slice(prefix.length)
|
|
61
|
+
set(newKey, value, prefixNode)
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
// 并非整个现有节点的前缀都被使用, 说明需要在这一级创建新的前缀节点,
|
|
65
|
+
// 并将原前缀节点作为子节点接在新前缀节点之后.
|
|
66
|
+
const oldPrefixNode = node.children.get(prefix)!
|
|
67
|
+
const commonPrefix = prefix.slice(0, commonPartLength)
|
|
68
|
+
const newPrefixNode = new TreeNode<T, V>()
|
|
69
|
+
node.children.set(commonPrefix, newPrefixNode)
|
|
70
|
+
newPrefixNode.children.set(prefix.slice(commonPartLength), oldPrefixNode)
|
|
71
|
+
newPrefixNode.children.set(key.slice(commonPartLength), new TreeNode<T, V>(value))
|
|
72
|
+
node.children.delete(prefix)
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
node.children.set(key, new TreeNode(value))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
has(key: K): boolean {
|
|
81
|
+
return has(toArray(key), this.root)
|
|
82
|
+
|
|
83
|
+
function has(key: T[], node: TreeNode<T, V>): boolean {
|
|
84
|
+
const matchedPrefix = matchPrefix(node.children.keys(), key)
|
|
85
|
+
if (isntUndefined(matchedPrefix)) {
|
|
86
|
+
const restKey = key.slice(matchedPrefix.length)
|
|
87
|
+
if (restKey.length === 0) {
|
|
88
|
+
return true
|
|
89
|
+
} else {
|
|
90
|
+
return has(restKey, node.children.get(matchedPrefix)!)
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get(key: K): V | undefined {
|
|
99
|
+
return get(toArray(key), this.root)
|
|
100
|
+
|
|
101
|
+
function get(key: T[], node: TreeNode<T, V>): V | undefined {
|
|
102
|
+
const matchedPrefix = matchPrefix(node.children.keys(), key)
|
|
103
|
+
if (isntUndefined(matchedPrefix)) {
|
|
104
|
+
const restKey = key.slice(matchedPrefix.length)
|
|
105
|
+
const nextNode = node.children.get(matchedPrefix)!
|
|
106
|
+
if (restKey.length === 0) {
|
|
107
|
+
return nextNode.value
|
|
108
|
+
} else {
|
|
109
|
+
return get(restKey, nextNode)
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
delete(key: K): boolean {
|
|
118
|
+
return _delete(toArray(key), this.root)
|
|
119
|
+
|
|
120
|
+
function _delete(key: T[], node: TreeNode<T, V>): boolean {
|
|
121
|
+
const matchedPrefix = matchPrefix(node.children.keys(), key)
|
|
122
|
+
if (isntUndefined(matchedPrefix)) {
|
|
123
|
+
if (key.length === matchedPrefix.length) {
|
|
124
|
+
// 下一个子节点就是目标节点.
|
|
125
|
+
const targetNode = node.children.get(matchedPrefix)!
|
|
126
|
+
if (targetNode.children.size === 0) {
|
|
127
|
+
// 要删除的目标节点不是一个父节点, 可以直接删除.
|
|
128
|
+
// 与目标节点共享相同前缀的其他节点可能会因此这次的删除而导致发生与父节点的合并.
|
|
129
|
+
node.children.delete(matchedPrefix)
|
|
130
|
+
} else {
|
|
131
|
+
// 要删除的目标节点是一个父节点, 首先删除目标节点包含的值, 然后检查它的子节点是否只有一个.
|
|
132
|
+
delete node.value
|
|
133
|
+
if (targetNode.children.size === 1) {
|
|
134
|
+
// 如果子节点只有一个, 说明目标节点的子节点可以与目标节点合并.
|
|
135
|
+
// 即`(prefix, child)`合并为`(prefixchild)`.
|
|
136
|
+
const [key, value] = first(targetNode.children.entries())!
|
|
137
|
+
node.children.set([...matchedPrefix, ...key], value)
|
|
138
|
+
node.children.delete(matchedPrefix)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return true
|
|
142
|
+
} else {
|
|
143
|
+
// 下一个子节点不是目标节点.
|
|
144
|
+
const nextNode = node.children.get(matchedPrefix)!
|
|
145
|
+
const nextKey = key.slice(matchedPrefix.length)
|
|
146
|
+
const deleted = _delete(nextKey, nextNode)
|
|
147
|
+
if (deleted) {
|
|
148
|
+
// 最终找到了目标节点并删除了它, 现在执行与nextNode有关的合并操作, 以删除不必要的节点.
|
|
149
|
+
if (!('value' in nextNode)) {
|
|
150
|
+
// 只有在nextNode自身不具有值的情况下, nextNode才是能够被合并的.
|
|
151
|
+
switch (nextNode.children.size) {
|
|
152
|
+
case 0: {
|
|
153
|
+
// 自身没有值的nextNode节点是不必要的, 因为它不是一个父节点.
|
|
154
|
+
node.children.delete(matchedPrefix)
|
|
155
|
+
break
|
|
156
|
+
}
|
|
157
|
+
case 1: {
|
|
158
|
+
// 自身没有值的nextNode节点是不必要的, 虽然它是一个父节点, 但它只有一个子节点.
|
|
159
|
+
// 这说明nextNode的子节点可以与nextNode合并, 替代nextNode当前的位置.
|
|
160
|
+
// 即`(prefix, child)`合并为`(prefixchild)`.
|
|
161
|
+
const [key, value] = first(nextNode.children.entries())!
|
|
162
|
+
node.children.set([...matchedPrefix, ...key], value)
|
|
163
|
+
node.children.delete(matchedPrefix)
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
default: {
|
|
167
|
+
// 自身没有值, 但起前缀作用的nextNode节点是必要的,
|
|
168
|
+
// 但在有后代被删除的情况下, 可能会需要合并子节点的前缀.
|
|
169
|
+
// 在此检查子节点的前缀, 如果子节点具有共同前缀, 则合并前缀.
|
|
170
|
+
const entries = toArray(nextNode.children.entries())
|
|
171
|
+
const keys = entries.map(([key]) => key)
|
|
172
|
+
const commonPrefix = getCommonPrefix(keys)
|
|
173
|
+
if (commonPrefix) {
|
|
174
|
+
const newPrefix = [...matchedPrefix, ...commonPrefix]
|
|
175
|
+
const childNode = new TreeNode<T, V>()
|
|
176
|
+
for (const [key, value] of entries) {
|
|
177
|
+
childNode.children.set(key.slice(commonPrefix.length), value)
|
|
178
|
+
}
|
|
179
|
+
node.children.set(newPrefix, childNode)
|
|
180
|
+
node.children.delete(matchedPrefix)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return true
|
|
186
|
+
} else {
|
|
187
|
+
// 最终没有找到目标节点.
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 查找prefixes里与path的前缀匹配的项目.
|
|
200
|
+
*
|
|
201
|
+
* @param prefixes 所有前缀都不能在首部有重合.
|
|
202
|
+
*/
|
|
203
|
+
export function matchPrefix<T>(prefixes: Iterable<T[]>, path: T[]): T[] | undefined {
|
|
204
|
+
return find(prefixes, prefix => path[0] === prefix[0])
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 查找prefixes与path的共同前缀, 结果可能是其中一整个prefix, 或者其中一个prefix的前缀部分.
|
|
209
|
+
*
|
|
210
|
+
* @param prefixes 所有前缀都不能在首部有重合.
|
|
211
|
+
*/
|
|
212
|
+
export function findCommonPrefix<T>(prefixes: Iterable<T[]>, path: T[]): {
|
|
213
|
+
prefix: T[]
|
|
214
|
+
commonPartLength: number
|
|
215
|
+
} | undefined {
|
|
216
|
+
for (const prefix of prefixes) {
|
|
217
|
+
let commonPartLength = 0
|
|
218
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
219
|
+
if (path[i] === prefix[i]) {
|
|
220
|
+
commonPartLength++
|
|
221
|
+
} else {
|
|
222
|
+
if (commonPartLength > 0) {
|
|
223
|
+
return { prefix, commonPartLength }
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (commonPartLength > 0) {
|
|
228
|
+
return { prefix, commonPartLength }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 返回一组可迭代值的共同前缀.
|
|
236
|
+
*/
|
|
237
|
+
export function getCommonPrefix<T>(paths: T[][]): T[] {
|
|
238
|
+
if (paths.length > 0) {
|
|
239
|
+
const commonPrefix: T[] = []
|
|
240
|
+
|
|
241
|
+
loop: for (
|
|
242
|
+
let elementIndex = 0, firstPathLength = paths[0].length
|
|
243
|
+
; elementIndex < firstPathLength
|
|
244
|
+
; elementIndex++
|
|
245
|
+
) {
|
|
246
|
+
const element = paths[0][elementIndex]
|
|
247
|
+
for (let pathIndex = 1; pathIndex < paths.length; pathIndex++) {
|
|
248
|
+
const path = paths[pathIndex]
|
|
249
|
+
if (elementIndex < path.length) {
|
|
250
|
+
if (path[elementIndex] !== element) {
|
|
251
|
+
break loop
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
break loop
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
commonPrefix.push(element)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return commonPrefix
|
|
261
|
+
} else {
|
|
262
|
+
return []
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SkipList } from '@utils/skip-list.js'
|
|
2
|
+
import { map } from 'iterable-operator'
|
|
3
|
+
|
|
4
|
+
export class SortedSet<T> implements Iterable<T> {
|
|
5
|
+
private skipList: SkipList<T>
|
|
6
|
+
|
|
7
|
+
get [Symbol.toStringTag](): string {
|
|
8
|
+
return this.constructor.name
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
[Symbol.iterator](): IterableIterator<T> {
|
|
12
|
+
return this.values()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor(compare: (a: T, b: T) => number) {
|
|
16
|
+
this.skipList = new SkipList(compare)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
values(): IterableIterator<T> {
|
|
20
|
+
return map(this.skipList.elements(), node => node.value!)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
has(value: T): boolean {
|
|
24
|
+
return this.skipList.has(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
add(value: T): void {
|
|
28
|
+
this.skipList.add(value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
delete(value: T): void {
|
|
32
|
+
this.skipList.delete(value)
|
|
33
|
+
}
|
|
34
|
+
}
|