@blackglory/structures 0.13.3 → 0.14.0
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/lib/array.js.map +1 -1
- package/lib/big-map.js.map +1 -1
- package/lib/big-set.js.map +1 -1
- package/lib/bit-set.js.map +1 -1
- package/lib/box.js +3 -3
- package/lib/box.js.map +1 -1
- package/lib/cons.js.map +1 -1
- package/lib/dynamic-typed-array.js +10 -10
- package/lib/dynamic-typed-array.js.map +1 -1
- package/lib/emitter.d.ts +1 -1
- package/lib/emitter.js.map +1 -1
- package/lib/expirable-map.js.map +1 -1
- package/lib/hash-map.js +4 -4
- package/lib/hash-map.js.map +1 -1
- package/lib/hash-set.js +4 -4
- package/lib/hash-set.js.map +1 -1
- package/lib/lru-map.js +6 -6
- package/lib/lru-map.js.map +1 -1
- package/lib/queue.js.map +1 -1
- package/lib/radix-tree.js.map +1 -1
- package/lib/sorted-set.js +3 -3
- package/lib/sorted-set.js.map +1 -1
- package/lib/sparse-map.js.map +1 -1
- package/lib/sparse-set.js +9 -9
- package/lib/sparse-set.js.map +1 -1
- package/lib/string-radix-tree.js.map +1 -1
- package/lib/string-trie-map.js +1 -1
- package/lib/string-trie-map.js.map +1 -1
- package/lib/tlru-map.js +6 -6
- package/lib/tlru-map.js.map +1 -1
- package/lib/trie-map.js +1 -1
- package/lib/trie-map.js.map +1 -1
- package/lib/typed-bit-set.js.map +1 -1
- package/lib/typed-sparse-map.js +6 -6
- package/lib/typed-sparse-map.js.map +1 -1
- package/lib/typed-sparse-set.js.map +1 -1
- package/lib/utils/skip-list.js +3 -3
- package/lib/utils/skip-list.js.map +1 -1
- package/package.json +31 -31
- 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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { TypedArrayConstructor, TypedArrayOfConstructor } from 'justypes'
|
|
2
|
+
import { assert } from '@blackglory/errors'
|
|
3
|
+
|
|
4
|
+
interface IDynamicTypedArrayOptions {
|
|
5
|
+
capacity?: number
|
|
6
|
+
growthFactor?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// 相关提案: https://github.com/tc39/proposal-resizablearraybuffer
|
|
10
|
+
export class DynamicTypedArray<T extends TypedArrayConstructor> {
|
|
11
|
+
private array: TypedArrayOfConstructor<T>
|
|
12
|
+
private _length: number = 0
|
|
13
|
+
private initialCapacity: number
|
|
14
|
+
readonly growthFactor: number
|
|
15
|
+
|
|
16
|
+
get internalTypedArray(): TypedArrayOfConstructor<T> {
|
|
17
|
+
return this.array
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get [Symbol.toStringTag](): string {
|
|
21
|
+
return this.constructor.name
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get BYTES_PER_ELEMENT(): number {
|
|
25
|
+
return this.typedArrayConstructor.BYTES_PER_ELEMENT
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 数组当前的容量.
|
|
30
|
+
*/
|
|
31
|
+
get capacity(): number {
|
|
32
|
+
return this.array.length
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 数组逻辑上的长度, 总是小于或等于capacity.
|
|
37
|
+
*/
|
|
38
|
+
get length(): number {
|
|
39
|
+
return this._length
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
private typedArrayConstructor: T
|
|
44
|
+
, {
|
|
45
|
+
capacity = 0
|
|
46
|
+
, growthFactor = 1.5
|
|
47
|
+
}: IDynamicTypedArrayOptions = {}
|
|
48
|
+
) {
|
|
49
|
+
assert(Number.isInteger(capacity), 'capacity must be an integer')
|
|
50
|
+
assert(capacity >= 0, 'capacity must be greater than or equal to 0')
|
|
51
|
+
assert(growthFactor >= 1, 'growthFactory must be greater than or equal to 1')
|
|
52
|
+
|
|
53
|
+
this.initialCapacity = capacity
|
|
54
|
+
this.array = new typedArrayConstructor(capacity) as TypedArrayOfConstructor<T>
|
|
55
|
+
this.growthFactor = growthFactor
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set(index: number, value: number): void {
|
|
59
|
+
if (index >= this.capacity) {
|
|
60
|
+
const newCapacity = computeNewCapacity(
|
|
61
|
+
this.capacity
|
|
62
|
+
, index + 1
|
|
63
|
+
, this.growthFactor
|
|
64
|
+
)
|
|
65
|
+
this.resize(newCapacity)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (index >= this._length) {
|
|
69
|
+
this._length = index + 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.array[index] = value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
setValues(index: number, values: TypedArrayOfConstructor<T>): void {
|
|
76
|
+
if (index + values.length >= this.capacity) {
|
|
77
|
+
const newCapacity = computeNewCapacity(
|
|
78
|
+
this.capacity
|
|
79
|
+
, index + values.length
|
|
80
|
+
, this.growthFactor
|
|
81
|
+
)
|
|
82
|
+
this.resize(newCapacity)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (index + values.length > this._length) {
|
|
86
|
+
this._length = index + values.length
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.internalTypedArray.set(values, index)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get(index: number): number | undefined {
|
|
93
|
+
return index < this._length
|
|
94
|
+
? this.array[index]
|
|
95
|
+
: undefined
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
push(...values: number[]): void {
|
|
99
|
+
const newLength = this._length + values.length
|
|
100
|
+
if (newLength > this.capacity) {
|
|
101
|
+
const newCapacity = computeNewCapacity(
|
|
102
|
+
this.capacity
|
|
103
|
+
, newLength
|
|
104
|
+
, this.growthFactor
|
|
105
|
+
)
|
|
106
|
+
this.resize(newCapacity)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const value of values) {
|
|
110
|
+
this.array[this._length++] = value
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pop(): number | undefined {
|
|
115
|
+
if (this._length > 0) {
|
|
116
|
+
const value = this.array[this._length - 1]
|
|
117
|
+
this._length--
|
|
118
|
+
|
|
119
|
+
const newCapacity = computeNewCapacity(
|
|
120
|
+
this.capacity
|
|
121
|
+
, this.length
|
|
122
|
+
, this.growthFactor
|
|
123
|
+
)
|
|
124
|
+
this.resize(newCapacity)
|
|
125
|
+
|
|
126
|
+
return value
|
|
127
|
+
} else {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
clear(): void {
|
|
133
|
+
this._length = 0
|
|
134
|
+
const newArray = new this.typedArrayConstructor(this.initialCapacity)
|
|
135
|
+
this.array = newArray as TypedArrayOfConstructor<T>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
sort(compare?: (a: number, b: number) => number): void {
|
|
139
|
+
const capacity = this.capacity
|
|
140
|
+
this.resize(this.length)
|
|
141
|
+
this.array.sort(compare)
|
|
142
|
+
this.resize(capacity)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private resize(newCapacity: number): void {
|
|
146
|
+
if (this.array.length === newCapacity) {
|
|
147
|
+
return
|
|
148
|
+
} else if (this.array.length < newCapacity) {
|
|
149
|
+
const newArray = new this.typedArrayConstructor(newCapacity)
|
|
150
|
+
newArray.set(this.array)
|
|
151
|
+
this.array = newArray as TypedArrayOfConstructor<T>
|
|
152
|
+
} else if (this.array.length > newCapacity) {
|
|
153
|
+
const newArray = new this.typedArrayConstructor(newCapacity)
|
|
154
|
+
// 不需要的部分将被舍弃.
|
|
155
|
+
for (let i = newCapacity; i--;) {
|
|
156
|
+
newArray[i] = this.array[i]
|
|
157
|
+
}
|
|
158
|
+
this.array = newArray as TypedArrayOfConstructor<T>
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function computeNewCapacity(
|
|
164
|
+
oldCapacity: number
|
|
165
|
+
, targetLength: number
|
|
166
|
+
, growthFactor: number
|
|
167
|
+
): number {
|
|
168
|
+
if (growthFactor === 1) {
|
|
169
|
+
return targetLength
|
|
170
|
+
} else {
|
|
171
|
+
if (oldCapacity === targetLength) {
|
|
172
|
+
return targetLength
|
|
173
|
+
} else if (oldCapacity < targetLength) {
|
|
174
|
+
// 增加容量以适配targetLength
|
|
175
|
+
let newCapacity = Math.max(oldCapacity, 1)
|
|
176
|
+
while (newCapacity < targetLength) {
|
|
177
|
+
newCapacity *= growthFactor
|
|
178
|
+
}
|
|
179
|
+
return Math.floor(newCapacity)
|
|
180
|
+
} else {
|
|
181
|
+
// 尝试减少容量以适配targetLength
|
|
182
|
+
|
|
183
|
+
// `targetLength = 0`是特殊情况,
|
|
184
|
+
// 此时无论怎么将oldCapacity除以growthFactory几次, 都不可能得到比targetLength小的结果.
|
|
185
|
+
if (targetLength === 0) {
|
|
186
|
+
return 0
|
|
187
|
+
} else {
|
|
188
|
+
let newCapacity = oldCapacity
|
|
189
|
+
while (true) {
|
|
190
|
+
const temp = newCapacity / growthFactor
|
|
191
|
+
if (temp < targetLength) {
|
|
192
|
+
break
|
|
193
|
+
} else {
|
|
194
|
+
newCapacity = temp
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return Math.floor(newCapacity)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
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
|
+
}
|