linked-list-typed 1.48.0 → 1.48.1

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.
@@ -7,9 +7,333 @@
7
7
  */
8
8
 
9
9
  import { isWeakKey, rangeCheck } from '../../utils';
10
- import { HashMapLinkedNode, HashMapOptions } from '../../types';
10
+ import { HashMapLinkedNode, HashMapOptions, HashMapStoreItem } from '../../types';
11
11
 
12
12
  export class HashMap<K = any, V = any> {
13
+ protected _store: { [key: string]: HashMapStoreItem<K, V> } = {};
14
+ protected _objMap: Map<object, V> = new Map();
15
+
16
+ /**
17
+ * The constructor function initializes a new instance of a class with optional elements and options.
18
+ * @param elements - The `elements` parameter is an iterable containing key-value pairs `[K, V]`. It
19
+ * is optional and defaults to an empty array `[]`. This parameter is used to initialize the map with
20
+ * key-value pairs.
21
+ * @param [options] - The `options` parameter is an optional object that can contain additional
22
+ * configuration options for the constructor. In this case, it has one property:
23
+ */
24
+ constructor(elements: Iterable<[K, V]> = [], options?: {
25
+ hashFn: (key: K) => string
26
+ }) {
27
+ if (options) {
28
+ const { hashFn } = options;
29
+ if (hashFn) {
30
+ this._hashFn = hashFn;
31
+
32
+ }
33
+ }
34
+ if (elements) {
35
+ this.setMany(elements);
36
+ }
37
+ }
38
+
39
+ protected _size = 0;
40
+
41
+ get size(): number {
42
+ return this._size;
43
+ }
44
+
45
+ isEmpty(): boolean {
46
+ return this.size === 0;
47
+ }
48
+
49
+ clear() {
50
+ this._store = {};
51
+ this._objMap.clear();
52
+ this._size = 0;
53
+ }
54
+
55
+ /**
56
+ * The `set` function adds a key-value pair to a map-like data structure, incrementing the size if
57
+ * the key is not already present.
58
+ * @param {K} key - The key parameter is the key used to identify the value in the data structure. It
59
+ * can be of any type, but if it is an object, it will be stored in a Map, otherwise it will be
60
+ * stored in a regular JavaScript object.
61
+ * @param {V} value - The value parameter represents the value that you want to associate with the
62
+ * key in the data structure.
63
+ */
64
+ set(key: K, value: V) {
65
+
66
+ if (this._isObjKey(key)) {
67
+ if (!this._objMap.has(key)) {
68
+ this._size++;
69
+ }
70
+ this._objMap.set(key, value);
71
+
72
+ } else {
73
+ const strKey = this._getNoObjKey(key);
74
+ if (this._store[strKey] === undefined) {
75
+ this._size++;
76
+ }
77
+ this._store[strKey] = { key, value };
78
+ }
79
+ }
80
+
81
+ /**
82
+ * The function "setMany" sets multiple key-value pairs in a map.
83
+ * @param elements - The `elements` parameter is an iterable containing key-value pairs. Each
84
+ * key-value pair is represented as an array with two elements: the key and the value.
85
+ */
86
+ setMany(elements: Iterable<[K, V]>) {
87
+ for (const [key, value] of elements) this.set(key, value);
88
+ }
89
+
90
+ /**
91
+ * The `get` function retrieves a value from a map based on a given key, either from an object map or
92
+ * a string map.
93
+ * @param {K} key - The `key` parameter is the key used to retrieve a value from the map. It can be
94
+ * of any type, but it should be compatible with the key type used when the map was created.
95
+ * @returns The method `get(key: K)` returns a value of type `V` if the key exists in the `_objMap`
96
+ * or `_store`, otherwise it returns `undefined`.
97
+ */
98
+ get(key: K): V | undefined {
99
+ if (this._isObjKey(key)) {
100
+ return this._objMap.get(key);
101
+ } else {
102
+ const strKey = this._getNoObjKey(key);
103
+ return this._store[strKey]?.value;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * The `has` function checks if a given key exists in the `_objMap` or `_store` based on whether it
109
+ * is an object key or not.
110
+ * @param {K} key - The parameter "key" is of type K, which means it can be any type.
111
+ * @returns The `has` method is returning a boolean value.
112
+ */
113
+ has(key: K): boolean {
114
+ if (this._isObjKey(key)) {
115
+ return this._objMap.has(key);
116
+ } else {
117
+ const strKey = this._getNoObjKey(key);
118
+ return strKey in this._store;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * The `delete` function removes an element from a map-like data structure based on the provided key.
124
+ * @param {K} key - The `key` parameter is the key of the element that you want to delete from the
125
+ * data structure.
126
+ * @returns The `delete` method returns a boolean value. It returns `true` if the key was
127
+ * successfully deleted from the map, and `false` if the key was not found in the map.
128
+ */
129
+ delete(key: K): boolean {
130
+ if (this._isObjKey(key)) {
131
+ if (this._objMap.has(key)) {
132
+ this._size--
133
+ }
134
+
135
+ return this._objMap.delete(key);
136
+ } else {
137
+ const strKey = this._getNoObjKey(key);
138
+ if (strKey in this._store) {
139
+ delete this._store[strKey];
140
+ this._size--;
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * The function returns an iterator that yields key-value pairs from both an object store and an
149
+ * object map.
150
+ */
151
+ * [Symbol.iterator](): IterableIterator<[K, V]> {
152
+ for (const node of Object.values(this._store)) {
153
+ yield [node.key, node.value] as [K, V];
154
+ }
155
+ for (const node of this._objMap) {
156
+ yield node as [K, V];
157
+ }
158
+ }
159
+
160
+ /**
161
+ * The function returns an iterator that yields key-value pairs from the object.
162
+ */
163
+ * entries(): IterableIterator<[K, V]> {
164
+ for (const item of this) {
165
+ yield item;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * The function `keys()` returns an iterator that yields all the keys of the object.
171
+ */
172
+ * keys(): IterableIterator<K> {
173
+ for (const [key] of this) {
174
+ yield key;
175
+ }
176
+ }
177
+
178
+ * values(): IterableIterator<V> {
179
+ for (const [, value] of this) {
180
+ yield value;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * The `every` function checks if every element in a HashMap satisfies a given predicate function.
186
+ * @param predicate - The predicate parameter is a function that takes four arguments: value, key,
187
+ * index, and map. It is used to test each element in the map against a condition. If the predicate
188
+ * function returns false for any element, the every() method will return false. If the predicate
189
+ * function returns true for all
190
+ * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
191
+ * to be used as `this` when executing the `predicate` function. If `thisArg` is provided, it will be
192
+ * passed as the `this` value to the `predicate` function. If `thisArg` is
193
+ * @returns The method is returning a boolean value. It returns true if the predicate function
194
+ * returns true for every element in the map, and false otherwise.
195
+ */
196
+ every(predicate: (value: V, key: K, index: number, map: HashMap<K, V>) => boolean, thisArg?: any): boolean {
197
+ let index = 0;
198
+ for (const [key, value] of this) {
199
+ if (!predicate.call(thisArg, value, key, index++, this)) {
200
+ return false;
201
+ }
202
+ }
203
+ return true;
204
+ }
205
+
206
+ /**
207
+ * The "some" function checks if at least one element in a HashMap satisfies a given predicate.
208
+ * @param predicate - The `predicate` parameter is a function that takes four arguments: `value`,
209
+ * `key`, `index`, and `map`. It is used to determine whether a specific condition is met for a given
210
+ * key-value pair in the `HashMap`.
211
+ * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
212
+ * to be used as `this` when executing the `predicate` function. If `thisArg` is provided, it will be
213
+ * passed as the `this` value to the `predicate` function. If `thisArg` is
214
+ * @returns a boolean value. It returns true if the predicate function returns true for any element
215
+ * in the map, and false otherwise.
216
+ */
217
+ some(predicate: (value: V, key: K, index: number, map: HashMap<K, V>) => boolean, thisArg?: any): boolean {
218
+ let index = 0;
219
+ for (const [key, value] of this) {
220
+ if (predicate.call(thisArg, value, key, index++, this)) {
221
+ return true;
222
+ }
223
+ }
224
+ return false;
225
+ }
226
+
227
+ /**
228
+ * The `forEach` function iterates over the elements of a HashMap and applies a callback function to
229
+ * each element.
230
+ * @param callbackfn - A function that will be called for each key-value pair in the HashMap. It
231
+ * takes four parameters:
232
+ * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
233
+ * to be used as `this` when executing the `callbackfn` function. If `thisArg` is provided, it will
234
+ * be passed as the `this` value inside the `callbackfn` function. If `thisArg
235
+ */
236
+ forEach(callbackfn: (value: V, key: K, index: number, map: HashMap<K, V>) => void, thisArg?: any): void {
237
+ let index = 0;
238
+ for (const [key, value] of this) {
239
+ callbackfn.call(thisArg, value, key, index++, this);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * The `map` function in TypeScript creates a new HashMap by applying a callback function to each
245
+ * key-value pair in the original HashMap.
246
+ * @param callbackfn - The callback function that will be called for each key-value pair in the
247
+ * HashMap. It takes four parameters:
248
+ * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
249
+ * to be used as `this` when executing the `callbackfn` function. If `thisArg` is provided, it will
250
+ * be passed as the `this` value to the `callbackfn` function. If `thisArg
251
+ * @returns The `map` method is returning a new `HashMap` object with the transformed values based on
252
+ * the provided callback function.
253
+ */
254
+ map<U>(callbackfn: (value: V, key: K, index: number, map: HashMap<K, V>) => U, thisArg?: any): HashMap<K, U> {
255
+ const resultMap = new HashMap<K, U>();
256
+ let index = 0;
257
+ for (const [key, value] of this) {
258
+ resultMap.set(key, callbackfn.call(thisArg, value, key, index++, this));
259
+ }
260
+ return resultMap;
261
+ }
262
+
263
+ /**
264
+ * The `filter` function creates a new HashMap containing key-value pairs from the original HashMap
265
+ * that satisfy a given predicate function.
266
+ * @param predicate - The predicate parameter is a function that takes four arguments: value, key,
267
+ * index, and map. It is used to determine whether an element should be included in the filtered map
268
+ * or not. The function should return a boolean value - true if the element should be included, and
269
+ * false otherwise.
270
+ * @param {any} [thisArg] - The `thisArg` parameter is an optional argument that specifies the value
271
+ * to be used as `this` when executing the `predicate` function. If `thisArg` is provided, it will be
272
+ * passed as the `this` value to the `predicate` function. If `thisArg` is
273
+ * @returns The `filter` method is returning a new `HashMap` object that contains the key-value pairs
274
+ * from the original `HashMap` that pass the provided `predicate` function.
275
+ */
276
+ filter(predicate: (value: V, key: K, index: number, map: HashMap<K, V>) => boolean, thisArg?: any): HashMap<K, V> {
277
+ const filteredMap = new HashMap<K, V>();
278
+ let index = 0;
279
+ for (const [key, value] of this) {
280
+ if (predicate.call(thisArg, value, key, index++, this)) {
281
+ filteredMap.set(key, value);
282
+ }
283
+ }
284
+ return filteredMap;
285
+ }
286
+
287
+ /**
288
+ * The `reduce` function iterates over the elements of a HashMap and applies a callback function to
289
+ * each element, accumulating a single value.
290
+ * @param callbackfn - The callback function that will be called for each element in the HashMap. It
291
+ * takes five parameters:
292
+ * @param {U} initialValue - The initialValue parameter is the initial value of the accumulator. It
293
+ * is the value that will be used as the first argument of the callback function when reducing the
294
+ * elements of the map.
295
+ * @returns The `reduce` method is returning the final value of the accumulator after iterating over
296
+ * all the elements in the `HashMap`.
297
+ */
298
+ reduce<U>(callbackfn: (accumulator: U, currentValue: V, currentKey: K, index: number, map: HashMap<K, V>) => U, initialValue: U): U {
299
+ let accumulator = initialValue;
300
+ let index = 0;
301
+ for (const [key, value] of this) {
302
+ accumulator = callbackfn(accumulator, value, key, index++, this);
303
+ }
304
+ return accumulator;
305
+ }
306
+
307
+ print(): void{
308
+ console.log([...this.entries()]);
309
+ }
310
+
311
+ protected _hashFn: (key: K) => string = (key: K) => String(key);
312
+
313
+ protected _isObjKey(key: any): key is (object | ((...args: any[]) => any)) {
314
+ const keyType = typeof key;
315
+ return (keyType === 'object' || keyType === 'function') && key !== null
316
+ }
317
+
318
+ protected _getNoObjKey(key: K): string {
319
+ const keyType = typeof key;
320
+
321
+ let strKey: string;
322
+ if (keyType !== "string" && keyType !== "number" && keyType !== "symbol") {
323
+ strKey = this._hashFn(key);
324
+ } else {
325
+ if (keyType === "number") {
326
+ // TODO numeric key should has its own hash
327
+ strKey = <string>key;
328
+ } else {
329
+ strKey = <string>key;
330
+ }
331
+ }
332
+ return strKey;
333
+ }
334
+ }
335
+
336
+ export class LinkedHashMap<K = any, V = any> {
13
337
 
14
338
  protected _noObjMap: Record<string, HashMapLinkedNode<K, V | undefined>> = {};
15
339
  protected _objMap = new WeakMap<object, HashMapLinkedNode<K, V | undefined>>();
@@ -108,49 +432,76 @@ export class HashMap<K = any, V = any> {
108
432
  */
109
433
  set(key: K, value?: V) {
110
434
  let node;
435
+ const isNewKey = !this.has(key); // Check if the key is new
111
436
 
112
437
  if (isWeakKey(key)) {
113
438
  const hash = this._objHashFn(key);
114
439
  node = this._objMap.get(hash);
115
440
 
116
- if (node) {
117
- // If the node already exists, update its value
118
- node.value = value;
119
- } else {
441
+ if (!node && isNewKey) {
120
442
  // Create new node
121
443
  node = { key: <K>hash, value, prev: this._tail, next: this._sentinel };
122
-
123
- // Add new nodes to _objMap and linked list
124
444
  this._objMap.set(hash, node);
445
+ } else if (node) {
446
+ // Update the value of an existing node
447
+ node.value = value;
125
448
  }
126
449
  } else {
127
450
  const hash = this._hashFn(key);
128
- // Non-object keys are handled in the same way as the original implementation
129
451
  node = this._noObjMap[hash];
130
- if (node) {
452
+
453
+ if (!node && isNewKey) {
454
+ this._noObjMap[hash] = node = { key, value, prev: this._tail, next: this._sentinel };
455
+ } else if (node) {
456
+ // Update the value of an existing node
131
457
  node.value = value;
458
+ }
459
+ }
460
+
461
+ if (node && isNewKey) {
462
+ // Update the head and tail of the linked list
463
+ if (this._size === 0) {
464
+ this._head = node;
465
+ this._sentinel.next = node;
132
466
  } else {
133
- this._noObjMap[hash] = node = {
134
- key,
135
- value,
136
- prev: this._tail,
137
- next: this._sentinel
138
- };
467
+ this._tail.next = node;
468
+ node.prev = this._tail; // Make sure that the prev of the new node points to the current tail node
139
469
  }
470
+ this._tail = node;
471
+ this._sentinel.prev = node;
472
+ this._size++;
140
473
  }
141
474
 
142
- if (this._size === 0) {
143
- this._head = node;
144
- this._sentinel.next = node;
475
+ return this._size;
476
+ }
477
+
478
+ has(key: K): boolean {
479
+ if (isWeakKey(key)) {
480
+ const hash = this._objHashFn(key);
481
+ return this._objMap.has(hash);
145
482
  } else {
146
- this._tail.next = node;
483
+ const hash = this._hashFn(key);
484
+ return hash in this._noObjMap;
147
485
  }
486
+ }
148
487
 
149
- this._tail = node;
150
- this._sentinel.prev = node;
151
- this._size++;
488
+ setMany(entries: Iterable<[K, V]>): void {
489
+ for (const entry of entries) {
490
+ const [key, value] = entry;
491
+ this.set(key, value);
492
+ }
493
+ }
152
494
 
153
- return this._size;
495
+ keys(): K[] {
496
+ const keys: K[] = [];
497
+ for (const [key] of this) keys.push(key);
498
+ return keys;
499
+ }
500
+
501
+ values(): V[] {
502
+ const values: V[] = [];
503
+ for (const [, value] of this) values.push(value);
504
+ return values;
154
505
  }
155
506
 
156
507
  /**
@@ -283,16 +634,25 @@ export class HashMap<K = any, V = any> {
283
634
  this._head = this._tail = this._sentinel.prev = this._sentinel.next = this._sentinel;
284
635
  }
285
636
 
637
+ clone(): LinkedHashMap<K, V> {
638
+ const cloned = new LinkedHashMap<K, V>([], { hashFn: this._hashFn, objHashFn: this._objHashFn });
639
+ for (const entry of this) {
640
+ const [key, value] = entry;
641
+ cloned.set(key, value);
642
+ }
643
+ return cloned;
644
+ }
645
+
286
646
  /**
287
- * Time Complexity: O(n), where n is the number of elements in the HashMap.
647
+ * Time Complexity: O(n), where n is the number of elements in the LinkedHashMap.
288
648
  * Space Complexity: O(1)
289
649
  *
290
- * The `forEach` function iterates over each element in a HashMap and executes a callback function on
650
+ * The `forEach` function iterates over each element in a LinkedHashMap and executes a callback function on
291
651
  * each element.
292
652
  * @param callback - The callback parameter is a function that will be called for each element in the
293
- * HashMap. It takes three arguments:
653
+ * LinkedHashMap. It takes three arguments:
294
654
  */
295
- forEach(callback: (element: [K, V], index: number, hashMap: HashMap<K, V>) => void) {
655
+ forEach(callback: (element: [K, V], index: number, hashMap: LinkedHashMap<K, V>) => void) {
296
656
  let index = 0;
297
657
  let node = this._head;
298
658
  while (node !== this._sentinel) {
@@ -302,15 +662,15 @@ export class HashMap<K = any, V = any> {
302
662
  }
303
663
 
304
664
  /**
305
- * The `filter` function takes a predicate function and returns a new HashMap containing only the
665
+ * The `filter` function takes a predicate function and returns a new LinkedHashMap containing only the
306
666
  * key-value pairs that satisfy the predicate.
307
667
  * @param predicate - The `predicate` parameter is a function that takes two arguments: `element` and
308
668
  * `map`.
309
- * @returns a new HashMap object that contains the key-value pairs from the original HashMap that
669
+ * @returns a new LinkedHashMap object that contains the key-value pairs from the original LinkedHashMap that
310
670
  * satisfy the given predicate function.
311
671
  */
312
- filter(predicate: (element: [K, V], index: number, map: HashMap<K, V>) => boolean): HashMap<K, V> {
313
- const filteredMap = new HashMap<K, V>();
672
+ filter(predicate: (element: [K, V], index: number, map: LinkedHashMap<K, V>) => boolean): LinkedHashMap<K, V> {
673
+ const filteredMap = new LinkedHashMap<K, V>();
314
674
  let index = 0;
315
675
  for (const [key, value] of this) {
316
676
  if (predicate([key, value], index, this)) {
@@ -322,14 +682,14 @@ export class HashMap<K = any, V = any> {
322
682
  }
323
683
 
324
684
  /**
325
- * The `map` function takes a callback function and returns a new HashMap with the values transformed
685
+ * The `map` function takes a callback function and returns a new LinkedHashMap with the values transformed
326
686
  * by the callback.
327
687
  * @param callback - The `callback` parameter is a function that takes two arguments: `element` and
328
688
  * `map`.
329
- * @returns a new HashMap object with the values mapped according to the provided callback function.
689
+ * @returns a new LinkedHashMap object with the values mapped according to the provided callback function.
330
690
  */
331
- map<NV>(callback: (element: [K, V], index: number, map: HashMap<K, V>) => NV): HashMap<K, NV> {
332
- const mappedMap = new HashMap<K, NV>();
691
+ map<NV>(callback: (element: [K, V], index: number, map: LinkedHashMap<K, V>) => NV): LinkedHashMap<K, NV> {
692
+ const mappedMap = new LinkedHashMap<K, NV>();
333
693
  let index = 0;
334
694
  for (const [key, value] of this) {
335
695
  const newValue = callback([key, value], index, this);
@@ -340,18 +700,18 @@ export class HashMap<K = any, V = any> {
340
700
  }
341
701
 
342
702
  /**
343
- * The `reduce` function iterates over the elements of a HashMap and applies a callback function to
703
+ * The `reduce` function iterates over the elements of a LinkedHashMap and applies a callback function to
344
704
  * each element, accumulating a single value.
345
705
  * @param callback - The callback parameter is a function that takes three arguments: accumulator,
346
- * element, and map. It is called for each element in the HashMap and is used to accumulate a single
706
+ * element, and map. It is called for each element in the LinkedHashMap and is used to accumulate a single
347
707
  * result.
348
708
  * @param {A} initialValue - The `initialValue` parameter is the initial value of the accumulator. It
349
709
  * is the value that will be passed as the first argument to the `callback` function when reducing
350
710
  * the elements of the map.
351
711
  * @returns The `reduce` function is returning the final value of the accumulator after iterating
352
- * over all the elements in the HashMap and applying the callback function to each element.
712
+ * over all the elements in the LinkedHashMap and applying the callback function to each element.
353
713
  */
354
- reduce<A>(callback: (accumulator: A, element: [K, V], index: number, map: HashMap<K, V>) => A, initialValue: A): A {
714
+ reduce<A>(callback: (accumulator: A, element: [K, V], index: number, map: LinkedHashMap<K, V>) => A, initialValue: A): A {
355
715
  let accumulator = initialValue;
356
716
  let index = 0;
357
717
  for (const entry of this) {
@@ -362,7 +722,7 @@ export class HashMap<K = any, V = any> {
362
722
  }
363
723
 
364
724
  /**
365
- * Time Complexity: O(n), where n is the number of elements in the HashMap.
725
+ * Time Complexity: O(n), where n is the number of elements in the LinkedHashMap.
366
726
  * Space Complexity: O(1)
367
727
  *
368
728
  * The above function is an iterator that yields key-value pairs from a linked list.
@@ -9,3 +9,5 @@ export type HashMapOptions<K> = {
9
9
  hashFn: (key: K) => string;
10
10
  objHashFn: (key: K) => object
11
11
  }
12
+
13
+ export type HashMapStoreItem<K, V> = { key: K, value: V };