@blackglory/structures 0.13.3 → 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/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/package.json
CHANGED
package/src/array.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function sliceArrayLeft<T>(arr: T[], num: number): T[] {
|
|
2
|
+
return arr.slice(0, num)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function sliceArrayRight<T>(arr: T[], num: number): T[] {
|
|
6
|
+
const startIndex = arr.length - num
|
|
7
|
+
return arr.slice(startIndex >= 0 ? startIndex : 0)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function truncateArrayLeft<T>(arr: T[], num: number): void {
|
|
11
|
+
if (arr.length > num) {
|
|
12
|
+
arr.length = num
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function truncateArrayRight<T>(arr: T[], num: number): void {
|
|
17
|
+
const startIndex = arr.length - num
|
|
18
|
+
arr.splice(0, startIndex)
|
|
19
|
+
}
|
package/src/big-map.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Map` that supports unlimited elements.
|
|
3
|
+
*
|
|
4
|
+
* Note that `BigMap` cannot preserve the insertion order of elements.
|
|
5
|
+
*/
|
|
6
|
+
export class BigMap<K, V> implements Iterable<[K, V]> {
|
|
7
|
+
_maps: Array<Map<K, V>> = []
|
|
8
|
+
|
|
9
|
+
get [Symbol.toStringTag](): string {
|
|
10
|
+
return this.constructor.name
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get size(): number {
|
|
14
|
+
return this._maps
|
|
15
|
+
.map(map => map.size)
|
|
16
|
+
.reduce((acc, cur) => acc + cur, 0)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* [Symbol.iterator](): IterableIterator<[K, V]> {
|
|
20
|
+
for (const map of this._maps) {
|
|
21
|
+
yield* map[Symbol.iterator]()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
set(key: K, value: V): void {
|
|
26
|
+
{
|
|
27
|
+
const map = this._maps.find(map => map.has(key))
|
|
28
|
+
if (map) {
|
|
29
|
+
map.set(key, value)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const map of this._maps) {
|
|
35
|
+
try {
|
|
36
|
+
map.set(key, value)
|
|
37
|
+
return
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (e instanceof RangeError) continue
|
|
40
|
+
throw e
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const map = new Map<K, V>()
|
|
45
|
+
map.set(key, value)
|
|
46
|
+
this._maps.push(map)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
has(key: K): boolean {
|
|
50
|
+
return this._maps.some(set => set.has(key))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get(key: K): V | undefined {
|
|
54
|
+
for (const map of this._maps) {
|
|
55
|
+
if (map.has(key)) {
|
|
56
|
+
return map.get(key)!
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
delete(key: K): boolean {
|
|
62
|
+
for (const [index, map] of this._maps.entries()) {
|
|
63
|
+
if (map.has(key)) {
|
|
64
|
+
map.delete(key)
|
|
65
|
+
|
|
66
|
+
if (map.size === 0) {
|
|
67
|
+
this._maps.splice(index, 1)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clear(): void {
|
|
78
|
+
this._maps = []
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
* entries(): IterableIterator<[K, V]> {
|
|
82
|
+
for (const map of this._maps) {
|
|
83
|
+
yield* map.entries()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
* keys(): IterableIterator<K> {
|
|
88
|
+
for (const map of this._maps) {
|
|
89
|
+
yield* map.keys()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
* values(): IterableIterator<V> {
|
|
94
|
+
for (const map of this._maps) {
|
|
95
|
+
yield* map.values()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/big-set.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Set` that supports unlimited elements.
|
|
3
|
+
*
|
|
4
|
+
* Note that `BigSet` cannot preserve the insertion order of elements.
|
|
5
|
+
*/
|
|
6
|
+
export class BigSet<T> implements Iterable<T> {
|
|
7
|
+
_sets: Array<Set<T>> = []
|
|
8
|
+
|
|
9
|
+
get [Symbol.toStringTag](): string {
|
|
10
|
+
return this.constructor.name
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get size(): number {
|
|
14
|
+
return this._sets
|
|
15
|
+
.map(set => set.size)
|
|
16
|
+
.reduce((acc, cur) => acc + cur, 0)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* [Symbol.iterator](): IterableIterator<T> {
|
|
20
|
+
for (const set of this._sets) {
|
|
21
|
+
yield* set[Symbol.iterator]()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
add(value: T): void {
|
|
26
|
+
if (this._sets.some(set => set.has(value))) return
|
|
27
|
+
|
|
28
|
+
for (const set of this._sets) {
|
|
29
|
+
try {
|
|
30
|
+
set.add(value)
|
|
31
|
+
return
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (e instanceof RangeError) continue
|
|
34
|
+
throw e
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const set = new Set<T>()
|
|
39
|
+
set.add(value)
|
|
40
|
+
this._sets.push(set)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
has(value: T): boolean {
|
|
44
|
+
return this._sets.some(set => set.has(value))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
delete(value: T): boolean {
|
|
48
|
+
for (const [index, set] of this._sets.entries()) {
|
|
49
|
+
if (set.has(value)) {
|
|
50
|
+
set.delete(value)
|
|
51
|
+
|
|
52
|
+
if (set.size === 0) {
|
|
53
|
+
this._sets.splice(index, 1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
clear(): void {
|
|
64
|
+
this._sets = []
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
* values(): IterableIterator<T> {
|
|
68
|
+
for (const set of this._sets) {
|
|
69
|
+
yield* set.values()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/bit-set.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { assert } from '@blackglory/errors'
|
|
2
|
+
import { trailingZeros } from '@utils/trailing-zeros.js'
|
|
3
|
+
|
|
4
|
+
export class BitSet {
|
|
5
|
+
private array: number[] = []
|
|
6
|
+
private length = 0
|
|
7
|
+
private _size = 0
|
|
8
|
+
|
|
9
|
+
constructor(private bitsPerElement: number = 8) {
|
|
10
|
+
assert(
|
|
11
|
+
Number.isInteger(bitsPerElement)
|
|
12
|
+
, 'The parameter bitsPerElement must be an integer'
|
|
13
|
+
)
|
|
14
|
+
assert(
|
|
15
|
+
bitsPerElement > 0
|
|
16
|
+
, 'The parameter bitsPerElement must be greater than 0'
|
|
17
|
+
)
|
|
18
|
+
// `31`是该数据结构中能够处理的最大值
|
|
19
|
+
assert(
|
|
20
|
+
bitsPerElement <= 31
|
|
21
|
+
, 'The mask of bitsPerElement must be less than or equal to 31'
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get [Symbol.toStringTag](): string {
|
|
26
|
+
return this.constructor.name
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get size(): number {
|
|
30
|
+
return this._size
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
[Symbol.iterator](): IterableIterator<number> {
|
|
34
|
+
return this.values()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
* values(): IterableIterator<number> {
|
|
38
|
+
if (this.length > 0) {
|
|
39
|
+
// maxArrayLength = Math.ceil(this.length / this.bitsPerElement)
|
|
40
|
+
const maxArrayLength = ~~(this.length / this.bitsPerElement) + 1
|
|
41
|
+
|
|
42
|
+
for (let index = 0; index < maxArrayLength; index++) {
|
|
43
|
+
let element = this.array[index]
|
|
44
|
+
let offset = 0
|
|
45
|
+
let indexOfBit: number
|
|
46
|
+
while ((indexOfBit = trailingZeros(element)) !== 32) {
|
|
47
|
+
yield index * this.bitsPerElement + offset + indexOfBit
|
|
48
|
+
offset += indexOfBit + 1
|
|
49
|
+
element >>= indexOfBit + 1
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_dumpBinaryStrings(): string[] {
|
|
56
|
+
const result: string[] = []
|
|
57
|
+
for (let i = 0; i < this.array.length; i++) {
|
|
58
|
+
const binary = this.array[i].toString(2)
|
|
59
|
+
if (binary.length < this.bitsPerElement) {
|
|
60
|
+
result.push('0'.repeat(this.bitsPerElement - binary.length) + binary)
|
|
61
|
+
} else {
|
|
62
|
+
result.push(binary)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
has(value: number): boolean {
|
|
69
|
+
const [index, mask] = this.getPosition(value)
|
|
70
|
+
|
|
71
|
+
return ((this.array[index] ?? 0) & mask) === mask
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
add(value: number): boolean {
|
|
75
|
+
assert(value >= 0, 'value must be greater than or equal to 0')
|
|
76
|
+
if (value >= this.length) {
|
|
77
|
+
this.length = value + 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const [index, mask] = this.getPosition(value)
|
|
81
|
+
|
|
82
|
+
const element = this.array[index] ?? 0
|
|
83
|
+
this.array[index] = element | mask
|
|
84
|
+
|
|
85
|
+
const added = (element & mask) !== mask
|
|
86
|
+
if (added) {
|
|
87
|
+
this._size++
|
|
88
|
+
}
|
|
89
|
+
return added
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
delete(value: number): boolean {
|
|
93
|
+
const [index, mask] = this.getPosition(value)
|
|
94
|
+
|
|
95
|
+
const element = (this.array[index] ?? 0)
|
|
96
|
+
this.array[index] = element & ~mask
|
|
97
|
+
|
|
98
|
+
const deleted = (element & mask) === mask
|
|
99
|
+
if (deleted) {
|
|
100
|
+
this._size--
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return deleted
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
clear(): void {
|
|
107
|
+
this._size = 0
|
|
108
|
+
this.array.length = 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getPosition(value: number): [index: number, mask: number] {
|
|
112
|
+
const remainder = value % this.bitsPerElement
|
|
113
|
+
const quotient = (value - remainder) / this.bitsPerElement
|
|
114
|
+
const index = quotient
|
|
115
|
+
const mask = this.getMask(remainder)
|
|
116
|
+
return [index, mask]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 输入一定是一个小于bitsPerElement的值, 取值范围是[0, bitsPerElement)
|
|
120
|
+
private getMask(value: number): number {
|
|
121
|
+
// return 2 ** value
|
|
122
|
+
return 1 << value
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/box.ts
ADDED
package/src/cons.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isNull } from 'extra-utils'
|
|
2
|
+
import { Cons } from 'justypes'
|
|
3
|
+
export { Cons } from 'justypes'
|
|
4
|
+
|
|
5
|
+
export function convertConsToArray<T>([value, next]: Cons<T>): T[] {
|
|
6
|
+
if (isNull(next)) {
|
|
7
|
+
return [value]
|
|
8
|
+
} else {
|
|
9
|
+
return [value, ...convertConsToArray(next)]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function convertArrayToCons<T>([value, ...next]: T[]): Cons<T> {
|
|
14
|
+
return [value, next.length ? convertArrayToCons(next) : null]
|
|
15
|
+
}
|
|
@@ -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
|
+
}
|