@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/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
+ }
@@ -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
+ }
@@ -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
+ }