data-structure-typed 1.33.5 → 1.33.7

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.
Files changed (51) hide show
  1. package/.github/workflows/ci.yml +1 -0
  2. package/CHANGELOG.md +1 -1
  3. package/README.md +18 -25
  4. package/coverage/coverage-final.json +64 -63
  5. package/coverage/coverage-summary.json +7 -6
  6. package/dist/data-structures/binary-tree/segment-tree.js +24 -6
  7. package/dist/data-structures/binary-tree/segment-tree.js.map +1 -1
  8. package/dist/data-structures/hash/hash-map.js +306 -0
  9. package/dist/data-structures/hash/hash-map.js.map +1 -0
  10. package/dist/data-structures/hash/hash-table.js +128 -38
  11. package/dist/data-structures/hash/hash-table.js.map +1 -1
  12. package/dist/data-structures/hash/index.js +1 -0
  13. package/dist/data-structures/hash/index.js.map +1 -1
  14. package/dist/data-structures/linked-list/skip-linked-list.js +122 -5
  15. package/dist/data-structures/linked-list/skip-linked-list.js.map +1 -1
  16. package/dist/types/data-structures/hash.js +3 -0
  17. package/dist/types/data-structures/hash.js.map +1 -0
  18. package/dist/types/data-structures/index.js +1 -0
  19. package/dist/types/data-structures/index.js.map +1 -1
  20. package/docs/index.html +23 -27
  21. package/docs/modules.html +10 -4
  22. package/lib/data-structures/binary-tree/segment-tree.d.ts +4 -4
  23. package/lib/data-structures/binary-tree/segment-tree.js +30 -14
  24. package/lib/data-structures/hash/hash-map.d.ts +56 -0
  25. package/lib/data-structures/hash/hash-map.js +167 -0
  26. package/lib/data-structures/hash/hash-table.d.ts +67 -23
  27. package/lib/data-structures/hash/hash-table.js +154 -52
  28. package/lib/data-structures/hash/index.d.ts +1 -0
  29. package/lib/data-structures/hash/index.js +1 -0
  30. package/lib/data-structures/linked-list/skip-linked-list.d.ts +60 -1
  31. package/lib/data-structures/linked-list/skip-linked-list.js +136 -1
  32. package/lib/types/data-structures/hash.d.ts +1 -0
  33. package/lib/types/data-structures/hash.js +1 -0
  34. package/lib/types/data-structures/index.d.ts +1 -0
  35. package/lib/types/data-structures/index.js +1 -0
  36. package/package.json +1 -1
  37. package/src/data-structures/binary-tree/segment-tree.ts +32 -14
  38. package/src/data-structures/hash/hash-map.ts +203 -0
  39. package/src/data-structures/hash/hash-table.ts +176 -56
  40. package/src/data-structures/hash/index.ts +1 -0
  41. package/src/data-structures/linked-list/skip-linked-list.ts +166 -1
  42. package/src/types/data-structures/hash.ts +1 -0
  43. package/src/types/data-structures/index.ts +1 -0
  44. package/test/unit/data-structures/binary-tree/segment-tree.test.ts +50 -0
  45. package/test/unit/data-structures/hash/hash-map.test.ts +104 -0
  46. package/test/unit/data-structures/hash/hash-table.test.ts +97 -10
  47. package/test/unit/data-structures/linked-list/doubly-linked-list.test.ts +1 -1
  48. package/test/unit/data-structures/linked-list/skip-list.test.ts +55 -0
  49. package/umd/bundle.min.js +1 -1
  50. package/umd/bundle.min.js.map +1 -1
  51. package/tsconfig.prod.json +0 -25
@@ -0,0 +1,203 @@
1
+ import {HashFunction} from '../../types';
2
+
3
+ /**
4
+ * data-structure-typed
5
+ *
6
+ * @author Tyler Zeng
7
+ * @copyright Copyright (c) 2022 Tyler Zeng <zrwusa@gmail.com>
8
+ * @license MIT License
9
+ */
10
+ export class HashMap<K, V> {
11
+ get hashFn(): HashFunction<K> {
12
+ return this._hashFn;
13
+ }
14
+
15
+ set hashFn(value: HashFunction<K>) {
16
+ this._hashFn = value;
17
+ }
18
+ get table(): Array<Array<[K, V]>> {
19
+ return this._table;
20
+ }
21
+
22
+ set table(value: Array<Array<[K, V]>>) {
23
+ this._table = value;
24
+ }
25
+
26
+ get capacityMultiplier(): number {
27
+ return this._capacityMultiplier;
28
+ }
29
+
30
+ set capacityMultiplier(value: number) {
31
+ this._capacityMultiplier = value;
32
+ }
33
+
34
+ get loadFactor(): number {
35
+ return this._loadFactor;
36
+ }
37
+
38
+ set loadFactor(value: number) {
39
+ this._loadFactor = value;
40
+ }
41
+
42
+ get initialCapacity(): number {
43
+ return this._initialCapacity;
44
+ }
45
+
46
+ set initialCapacity(value: number) {
47
+ this._initialCapacity = value;
48
+ }
49
+
50
+ get size(): number {
51
+ return this._size;
52
+ }
53
+
54
+ set size(value: number) {
55
+ this._size = value;
56
+ }
57
+
58
+ private _initialCapacity: number;
59
+ private _loadFactor: number;
60
+ private _capacityMultiplier: number;
61
+ private _size: number;
62
+ private _table: Array<Array<[K, V]>>;
63
+ private _hashFn: HashFunction<K>;
64
+
65
+ /**
66
+ * The constructor initializes the properties of a hash table, including the initial capacity, load factor, capacity
67
+ * multiplier, size, table array, and hash function.
68
+ * @param [initialCapacity=16] - The initial capacity is the initial size of the hash table. It determines the number of
69
+ * buckets or slots available for storing key-value pairs. The default value is 16.
70
+ * @param [loadFactor=0.75] - The load factor is a measure of how full the hash table can be before it is resized. It is
71
+ * a value between 0 and 1, where 1 means the hash table is completely full and 0 means it is completely empty. When the
72
+ * load factor is reached, the hash table will
73
+ * @param [hashFn] - The `hashFn` parameter is an optional parameter that represents the hash function used to calculate
74
+ * the index of a key in the hash table. If a custom hash function is not provided, a default hash function is used. The
75
+ * default hash function converts the key to a string, calculates the sum of the
76
+ */
77
+ constructor(initialCapacity = 16, loadFactor = 0.75, hashFn?: HashFunction<K>) {
78
+ this._initialCapacity = initialCapacity;
79
+ this._loadFactor = loadFactor;
80
+ this._capacityMultiplier = 2;
81
+ this._size = 0;
82
+ this._table = new Array(initialCapacity);
83
+ this._hashFn =
84
+ hashFn ||
85
+ ((key: K) => {
86
+ const strKey = String(key);
87
+ let hash = 0;
88
+ for (let i = 0; i < strKey.length; i++) {
89
+ hash += strKey.charCodeAt(i);
90
+ }
91
+ return hash % this.table.length;
92
+ });
93
+ }
94
+
95
+ private _hash(key: K): number {
96
+ return this._hashFn(key);
97
+ }
98
+
99
+ /**
100
+ * The `resizeTable` function resizes the table used in a hash map by creating a new table with a specified capacity and
101
+ * rehashing the key-value pairs from the old table into the new table.
102
+ * @param {number} newCapacity - The newCapacity parameter is the desired capacity for the resized table. It represents
103
+ * the number of buckets that the new table should have.
104
+ */
105
+ private resizeTable(newCapacity: number): void {
106
+ const newTable = new Array(newCapacity);
107
+ for (const bucket of this._table) {
108
+ // Note that this is this._table
109
+ if (bucket) {
110
+ for (const [key, value] of bucket) {
111
+ const newIndex = this._hash(key) % newCapacity;
112
+ if (!newTable[newIndex]) {
113
+ newTable[newIndex] = [];
114
+ }
115
+ newTable[newIndex].push([key, value]);
116
+ }
117
+ }
118
+ }
119
+ this._table = newTable; // Again, here is this._table
120
+ }
121
+
122
+ set(key: K, value: V): void {
123
+ const loadFactor = this.size / this.table.length;
124
+ if (loadFactor >= this.loadFactor) {
125
+ this.resizeTable(this.table.length * this.capacityMultiplier);
126
+ }
127
+
128
+ const index = this._hash(key);
129
+ if (!this.table[index]) {
130
+ this.table[index] = [];
131
+ }
132
+
133
+ // Check if the key already exists in the bucket
134
+ for (let i = 0; i < this.table[index].length; i++) {
135
+ if (this.table[index][i][0] === key) {
136
+ this.table[index][i][1] = value;
137
+ return;
138
+ }
139
+ }
140
+
141
+ this.table[index].push([key, value]);
142
+ this.size++;
143
+ }
144
+
145
+ get(key: K): V | undefined {
146
+ const index = this._hash(key);
147
+ if (!this.table[index]) {
148
+ return undefined;
149
+ }
150
+
151
+ for (const [k, v] of this.table[index]) {
152
+ if (k === key) {
153
+ return v;
154
+ }
155
+ }
156
+
157
+ return undefined;
158
+ }
159
+
160
+ remove(key: K): void {
161
+ const index = this._hash(key);
162
+ if (!this.table[index]) {
163
+ return;
164
+ }
165
+
166
+ for (let i = 0; i < this.table[index].length; i++) {
167
+ if (this.table[index][i][0] === key) {
168
+ this.table[index].splice(i, 1);
169
+ this.size--;
170
+
171
+ // Check if the table needs to be resized down
172
+ const loadFactor = this.size / this.table.length;
173
+ if (loadFactor < this.loadFactor / this.capacityMultiplier) {
174
+ this.resizeTable(this.table.length / this.capacityMultiplier);
175
+ }
176
+ return;
177
+ }
178
+ }
179
+ }
180
+
181
+ *entries(): IterableIterator<[K, V]> {
182
+ for (const bucket of this.table) {
183
+ if (bucket) {
184
+ for (const [key, value] of bucket) {
185
+ yield [key, value];
186
+ }
187
+ }
188
+ }
189
+ }
190
+
191
+ [Symbol.iterator](): IterableIterator<[K, V]> {
192
+ return this.entries();
193
+ }
194
+
195
+ clear(): void {
196
+ this.size = 0;
197
+ this.table = new Array(this.initialCapacity);
198
+ }
199
+
200
+ isEmpty(): boolean {
201
+ return this.size === 0;
202
+ }
203
+ }
@@ -5,10 +5,11 @@
5
5
  * @copyright Copyright (c) 2022 Tyler Zeng <zrwusa@gmail.com>
6
6
  * @license MIT License
7
7
  */
8
- export class HashNode<K, V> {
8
+
9
+ export class HashTableNode<K, V> {
9
10
  key: K;
10
11
  val: V;
11
- next: HashNode<K, V> | null;
12
+ next: HashTableNode<K, V> | null;
12
13
 
13
14
  constructor(key: K, val: V) {
14
15
  this.key = key;
@@ -17,21 +18,23 @@ export class HashNode<K, V> {
17
18
  }
18
19
  }
19
20
 
21
+ import {HashFunction} from '../../types';
22
+
20
23
  export class HashTable<K, V> {
21
- get buckets(): Array<HashNode<K, V> | null> {
22
- return this._buckets;
24
+ get hashFn(): HashFunction<K> {
25
+ return this._hashFn;
23
26
  }
24
27
 
25
- set buckets(value: Array<HashNode<K, V> | null>) {
26
- this._buckets = value;
28
+ set hashFn(value: HashFunction<K>) {
29
+ this._hashFn = value;
27
30
  }
28
31
 
29
- get size(): number {
30
- return this._size;
32
+ get buckets(): Array<HashTableNode<K, V> | null> {
33
+ return this._buckets;
31
34
  }
32
35
 
33
- set size(value: number) {
34
- this._size = value;
36
+ set buckets(value: Array<HashTableNode<K, V> | null>) {
37
+ this._buckets = value;
35
38
  }
36
39
 
37
40
  get capacity(): number {
@@ -42,82 +45,163 @@ export class HashTable<K, V> {
42
45
  this._capacity = value;
43
46
  }
44
47
 
48
+ private static readonly DEFAULT_CAPACITY = 16;
49
+ private static readonly LOAD_FACTOR = 0.75;
50
+
45
51
  private _capacity: number;
46
52
  private _size: number;
47
- private _buckets: Array<HashNode<K, V> | null>;
53
+ private _buckets: Array<HashTableNode<K, V> | null>;
54
+ private _hashFn: HashFunction<K>;
55
+
56
+ constructor(capacity: number = HashTable.DEFAULT_CAPACITY, hashFn?: HashFunction<K>) {
57
+ this._hashFn = hashFn || this._defaultHashFn;
58
+ this._capacity = Math.max(capacity, HashTable.DEFAULT_CAPACITY);
59
+ this._size = 0;
60
+ this._buckets = new Array<HashTableNode<K, V> | null>(this._capacity).fill(null);
61
+ }
48
62
 
49
63
  /**
50
- * The constructor initializes the capacity, size, and buckets of an object.
51
- * @param [capacity=1000] - The `capacity` parameter represents the maximum number of elements that the data structure
52
- * can hold. It is an optional parameter with a default value of 1000.
64
+ * The function `_defaultHashFn` calculates the hash value of a given key and returns the remainder when divided by the
65
+ * capacity of the data structure.
66
+ * @param {K} key - The `key` parameter is the input value that needs to be hashed. It can be of any type, but in this
67
+ * code snippet, it is checked whether the key is a string or an object. If it is a string, the `_murmurStringHashFn`
68
+ * function is used to
69
+ * @returns the hash value of the key modulo the capacity of the data structure.
53
70
  */
54
- constructor(capacity = 1000) {
55
- this._capacity = capacity;
56
- this._size = 0;
57
- this._buckets = new Array(this.capacity).fill(null);
71
+ protected _defaultHashFn(key: K): number {
72
+ // Can be replaced with other hash functions as needed
73
+ const hashValue = typeof key === 'string' ? this._murmurStringHashFn(key) : this._objectHash(key);
74
+ return hashValue % this._capacity;
58
75
  }
59
76
 
60
77
  /**
61
- * The hash function takes a key, converts it to a string, calculates the sum of the ASCII values of its characters, and
62
- * returns the remainder when divided by the capacity of the data structure.
63
- * @param {K} key - The `key` parameter represents the key that needs to be hashed. It is of type `K`, which means it can
64
- * be any data type that can be converted to a string.
65
- * @returns The hash value of the key modulo the capacity of the data structure.
78
+ * The `_multiplicativeStringHashFn` function calculates a hash value for a given string key using the multiplicative
79
+ * string hash function.
80
+ * @param {K} key - The `key` parameter is the input value for which we want to calculate the hash. It can be of any
81
+ * type, as it is generic (`K`). The function converts the `key` to a string using the `String()` function.
82
+ * @returns a number, which is the result of the multiplicative string hash function applied to the input key.
66
83
  */
67
- private hash(key: K): number {
84
+ protected _multiplicativeStringHashFn<K>(key: K): number {
68
85
  const keyString = String(key);
69
86
  let hash = 0;
70
87
  for (let i = 0; i < keyString.length; i++) {
71
- hash += keyString.charCodeAt(i);
88
+ const charCode = keyString.charCodeAt(i);
89
+ // Some constants for adjusting the hash function
90
+ const A = 0.618033988749895;
91
+ const M = 1 << 30; // 2^30
92
+ hash = (hash * A + charCode) % M;
93
+ }
94
+ return Math.abs(hash); // Take absolute value to ensure non-negative numbers
95
+ }
96
+
97
+ /**
98
+ * The function `_murmurStringHashFn` calculates a hash value for a given string key using the MurmurHash algorithm.
99
+ * @param {K} key - The `key` parameter is the input value for which you want to calculate the hash. It can be of any
100
+ * type, but it will be converted to a string using the `String()` function before calculating the hash.
101
+ * @returns a number, which is the hash value calculated for the given key.
102
+ */
103
+ protected _murmurStringHashFn<K>(key: K): number {
104
+ const keyString = String(key);
105
+ const seed = 0;
106
+ let hash = seed;
107
+
108
+ for (let i = 0; i < keyString.length; i++) {
109
+ const char = keyString.charCodeAt(i);
110
+ hash = (hash ^ char) * 0x5bd1e995;
111
+ hash = (hash ^ (hash >>> 15)) * 0x27d4eb2d;
112
+ hash = hash ^ (hash >>> 15);
113
+ }
114
+
115
+ return Math.abs(hash);
116
+ }
117
+
118
+ /**
119
+ * The _hash function takes a key and returns a number.
120
+ * @param {K} key - The parameter "key" is of type K, which represents the type of the key that will be hashed.
121
+ * @returns The hash function is returning a number.
122
+ */
123
+ protected _hash(key: K): number {
124
+ return this.hashFn(key);
125
+ }
126
+
127
+ /**
128
+ * The function calculates a hash value for a given string using the djb2 algorithm.
129
+ * @param {string} key - The `key` parameter in the `stringHash` function is a string value that represents the input for
130
+ * which we want to calculate the hash value.
131
+ * @returns a number, which is the hash value of the input string.
132
+ */
133
+ protected _stringHash(key: string): number {
134
+ let hash = 0;
135
+ for (let i = 0; i < key.length; i++) {
136
+ hash = (hash * 31 + key.charCodeAt(i)) & 0xffffffff;
72
137
  }
73
- return hash % this.capacity;
138
+ return hash;
139
+ }
140
+
141
+ /**
142
+ * The function `_objectHash` takes a key and returns a hash value, using a custom hash function for objects.
143
+ * @param {K} key - The parameter "key" is of type "K", which means it can be any type. It could be a string, number,
144
+ * boolean, object, or any other type of value. The purpose of the objectHash function is to generate a hash value for
145
+ * the key, which can be used for
146
+ * @returns a number, which is the hash value of the key.
147
+ */
148
+ protected _objectHash(key: K): number {
149
+ // If the key is an object, you can write a custom hash function
150
+ // For example, convert the object's properties to a string and use string hashing
151
+ // This is just an example; you should write a specific object hash function as needed
152
+ return this._stringHash(JSON.stringify(key));
74
153
  }
75
154
 
76
155
  /**
77
- * The put function adds a key-value pair to a hash table, handling collisions by chaining.
156
+ * The set function adds a key-value pair to the hash table, handling collisions and resizing if necessary.
78
157
  * @param {K} key - The key parameter represents the key of the key-value pair that you want to insert into the hash
79
- * table. It is of type K, which can be any data type that can be used as a key, such as a string, number, or object.
80
- * @param {V} val - The `val` parameter represents the value associated with the key in the hash table.
81
- * @returns Nothing is being returned. The return type of the function is void, which means it does not return any value.
158
+ * table. It is of type K, which is a generic type representing the key's data type.
159
+ * @param {V} val - The parameter `val` represents the value that you want to associate with the given key in the hash
160
+ * table.
161
+ * @returns Nothing is being returned. The return type of the `put` method is `void`, which means it does not return any
162
+ * value.
82
163
  */
83
- put(key: K, val: V): void {
84
- const index = this.hash(key);
85
- const newNode = new HashNode(key, val);
164
+ set(key: K, val: V): void {
165
+ const index = this._hash(key);
166
+ const newNode = new HashTableNode<K, V>(key, val);
86
167
 
87
- if (!this.buckets[index]) {
88
- this.buckets[index] = newNode;
168
+ if (!this._buckets[index]) {
169
+ this._buckets[index] = newNode;
89
170
  } else {
90
- // Handle collision by chaining
91
- let currentNode = this.buckets[index]!;
92
- while (currentNode.next) {
171
+ // Handle collisions, consider using open addressing, etc.
172
+ let currentNode = this._buckets[index]!;
173
+ while (currentNode) {
93
174
  if (currentNode.key === key) {
94
- // Update the val if the key already exists
175
+ // If the key already exists, update the value
95
176
  currentNode.val = val;
96
177
  return;
97
178
  }
179
+ if (!currentNode.next) {
180
+ break;
181
+ }
98
182
  currentNode = currentNode.next;
99
183
  }
100
- if (currentNode.key === key) {
101
- // Update the val if the key already exists (last node)
102
- currentNode.val = val;
103
- } else {
104
- // Add the new node to the end of the chain
105
- currentNode.next = newNode;
106
- }
184
+ // Add to the end of the linked list
185
+ currentNode.next = newNode;
186
+ }
187
+ this._size++;
188
+
189
+ // If the load factor is too high, resize the hash table
190
+ if (this._size / this._capacity >= HashTable.LOAD_FACTOR) {
191
+ this._expand();
107
192
  }
108
- this.size++;
109
193
  }
110
194
 
111
195
  /**
112
196
  * The `get` function retrieves the value associated with a given key from a hash table.
113
- * @param {K} key - The parameter "key" represents the key of the element that we want to retrieve from the data
197
+ * @param {K} key - The `key` parameter represents the key of the element that we want to retrieve from the data
114
198
  * structure.
115
199
  * @returns The method is returning the value associated with the given key if it exists in the hash table. If the key is
116
200
  * not found, it returns `undefined`.
117
201
  */
118
202
  get(key: K): V | undefined {
119
- const index = this.hash(key);
120
- let currentNode = this.buckets[index];
203
+ const index = this._hash(key);
204
+ let currentNode = this._buckets[index];
121
205
 
122
206
  while (currentNode) {
123
207
  if (currentNode.key === key) {
@@ -129,29 +213,65 @@ export class HashTable<K, V> {
129
213
  }
130
214
 
131
215
  /**
132
- * The `remove` function removes a key-value pair from a hash table.
216
+ * The remove function removes a key-value pair from a hash table.
133
217
  * @param {K} key - The `key` parameter represents the key of the key-value pair that needs to be removed from the hash
134
218
  * table.
135
219
  * @returns Nothing is being returned. The `remove` method has a return type of `void`, which means it does not return
136
220
  * any value.
137
221
  */
138
222
  remove(key: K): void {
139
- const index = this.hash(key);
140
- let currentNode = this.buckets[index];
141
- let prevNode: HashNode<K, V> | null = null;
223
+ const index = this._hash(key);
224
+ let currentNode = this._buckets[index];
225
+ let prevNode: HashTableNode<K, V> | null = null;
142
226
 
143
227
  while (currentNode) {
144
228
  if (currentNode.key === key) {
145
229
  if (prevNode) {
146
230
  prevNode.next = currentNode.next;
147
231
  } else {
148
- this.buckets[index] = currentNode.next;
232
+ this._buckets[index] = currentNode.next;
149
233
  }
150
- this.size--;
234
+ this._size--;
235
+ currentNode.next = null; // Release memory
151
236
  return;
152
237
  }
153
238
  prevNode = currentNode;
154
239
  currentNode = currentNode.next;
155
240
  }
156
241
  }
242
+
243
+ /**
244
+ * The `expand` function increases the capacity of a hash table by creating a new array of buckets with double the
245
+ * capacity and rehashing all the existing key-value pairs into the new buckets.
246
+ */
247
+ protected _expand(): void {
248
+ const newCapacity = this._capacity * 2;
249
+ const newBuckets = new Array<HashTableNode<K, V> | null>(newCapacity).fill(null);
250
+
251
+ for (const bucket of this._buckets) {
252
+ let currentNode = bucket;
253
+ while (currentNode) {
254
+ const newIndex = this._hash(currentNode.key);
255
+ const newNode = new HashTableNode<K, V>(currentNode.key, currentNode.val);
256
+
257
+ if (!newBuckets[newIndex]) {
258
+ newBuckets[newIndex] = newNode;
259
+ } else {
260
+ let currentNewNode = newBuckets[newIndex]!;
261
+ while (currentNewNode.next) {
262
+ currentNewNode = currentNewNode.next;
263
+ }
264
+ currentNewNode.next = newNode;
265
+ }
266
+ currentNode = currentNode.next;
267
+ }
268
+ }
269
+
270
+ this._buckets = newBuckets;
271
+ this._capacity = newCapacity;
272
+ }
273
+
274
+ get size(): number {
275
+ return this._size;
276
+ }
157
277
  }
@@ -4,3 +4,4 @@ export * from './coordinate-set';
4
4
  export * from './pair';
5
5
  export * from './tree-map';
6
6
  export * from './tree-set';
7
+ export * from './hash-map';