@raikuxq/alg-ds 1.0.2 → 1.1.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/README.md CHANGED
@@ -30,6 +30,7 @@ Clone this repository and install dependencies by using `yarn` command.
30
30
  + [Stack](#stack)
31
31
  + [Queue](#queue)
32
32
  + [Non-linear data structures](#non-linear-data-structures)
33
+ + [Hash table](#hash-table)
33
34
  + [Graph](#graph)
34
35
  + [Binary tree](#binary-trees)
35
36
 
@@ -135,6 +136,17 @@ Extends [IConvertableToArray](src/types/IConvertableToArray.ts) interface.
135
136
 
136
137
  # Non linear data structures
137
138
 
139
+ ## Hash Table
140
+ ### Interfaces
141
+ [IKeyValueStorage](src/types/IKeyValueStorage.ts) — Contains basic key-value storages operations.
142
+
143
+ ### Implementation
144
+ [HashTableNode](src/data-structures/HashTable/HashTableNode.ts) — Contains key, data and isDeleted properties.
145
+
146
+ [HashTable](src/data-structures/HashTable/HashTable.ts) [ [ tests ] ](test/unit/data-structures/hash-table/hash-table.test.ts) — Implementation of open addressing hash table using quadratic probing
147
+
148
+
149
+
138
150
  ## Graph
139
151
  ### Interfaces
140
152
  [IGraph](src/types/IGraph.ts) — Contains basic graph operations.
package/package.json CHANGED
@@ -30,6 +30,5 @@
30
30
  "typescript": "^4.0.3"
31
31
  },
32
32
  "dependencies": {},
33
- "version": "1.0.2",
34
- "type": "module"
33
+ "version": "1.1.0"
35
34
  }
@@ -0,0 +1,202 @@
1
+ import IKeyValueStorage from "../../types/IKeyValueStorage";
2
+ import HashTableNode from "./HashTableNode";
3
+
4
+ /**
5
+ * Implementation of open addressing hash table using quadratic probing
6
+ */
7
+ export default class HashTable<T> implements IKeyValueStorage<T> {
8
+ /**
9
+ Constants
10
+ */
11
+ private static DEFAULT_MAX_CAPACITY = 100;
12
+ private static MAX_LOAD_FACTOR = 0.5;
13
+
14
+ /**
15
+ * State
16
+ */
17
+ private storage: Array<HashTableNode<T>>;
18
+ private maxCapacity: number;
19
+ private storageCapacity = 0;
20
+
21
+ /**
22
+ * Given init capacity size will be used
23
+ * @throws when initial capacity value is not larger than 0
24
+ */
25
+ public constructor(initialCapacity: number = HashTable.DEFAULT_MAX_CAPACITY) {
26
+ if (initialCapacity <= 0) {
27
+ throw new Error("Size must be larger than 0");
28
+ }
29
+ this.maxCapacity = initialCapacity;
30
+ this.storage = new Array(this.maxCapacity).fill(null);
31
+ }
32
+
33
+ /**
34
+ * Main-hash function
35
+ */
36
+ private hashFn(key: string, number: number): number {
37
+ return (
38
+ (this.innerHashFn(key) + 127 * number + 365 * number * number) %
39
+ this.maxCapacity
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Helper-hash function
45
+ */
46
+ private innerHashFn(key: string): number {
47
+ const length: number = key.length;
48
+
49
+ if (length === 0) {
50
+ return 0;
51
+ }
52
+ const substring = key.substring(0, length - 1);
53
+ const symbol = key.charCodeAt(length - 1);
54
+
55
+ return (127 * this.innerHashFn(substring) + symbol) % this.maxCapacity;
56
+ }
57
+
58
+ /**
59
+ * Max capacity will be increased and storage will be overwritten
60
+ */
61
+ private resizeStorage(): Array<HashTableNode<T>> {
62
+ this.maxCapacity *= 2;
63
+
64
+ const newArray = new Array(this.maxCapacity).fill(null);
65
+
66
+ for (let i = 0; i < this.storage.length; i++) {
67
+ const element = this.storage[i];
68
+
69
+ if (element != null) {
70
+ for (let j = 0; j < this.maxCapacity; j++) {
71
+ const newIndex = this.hashFn(element.key, j);
72
+
73
+ if (newArray[newIndex] == null) {
74
+ newArray[newIndex] = element;
75
+
76
+ break;
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ return newArray;
83
+ }
84
+
85
+ /**
86
+ * Will find node instance by its key
87
+ * @throws when element does not exist
88
+ */
89
+ private findNode(key: string): HashTableNode<T> {
90
+ for (let i = 0; i < this.maxCapacity; i++) {
91
+ const index = this.hashFn(key, i);
92
+ const node = this.storage[index];
93
+
94
+ if (node?.key === key) {
95
+ if (node?.isDeleted) {
96
+ break;
97
+ }
98
+ return node;
99
+ }
100
+ }
101
+
102
+ throw new Error("Element does not exist");
103
+ }
104
+
105
+ /**
106
+ * Will create new node instance and set in to storage by index
107
+ */
108
+ private addNode(key: string, data: T, index: number): void {
109
+ this.storage[index] = new HashTableNode<T>(key, data);
110
+
111
+ this.storageCapacity++;
112
+ const loadFactor = this.storageCapacity / this.maxCapacity;
113
+
114
+ if (loadFactor >= HashTable.MAX_LOAD_FACTOR) {
115
+ this.storage = this.resizeStorage();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Will create new node instance and set in to storage by index
121
+ */
122
+ private updateNode(data: T, index: number): void {
123
+ this.storage[index].data = data;
124
+ this.storage[index].isDeleted = false;
125
+ }
126
+
127
+ /**
128
+ * Will insert item to hash table
129
+ */
130
+ public set(key: string, data: T): void {
131
+ for (let i = 0; i < this.maxCapacity; i++) {
132
+ const index = this.hashFn(key, i);
133
+ const node = this.storage[index];
134
+
135
+ const shouldAddNode = node === null;
136
+ if (shouldAddNode) {
137
+ this.addNode(key, data, index);
138
+ break;
139
+ }
140
+
141
+ const shouldUpdateNode = node.key === key;
142
+ if (shouldUpdateNode) {
143
+ this.updateNode(data, index);
144
+ break;
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Will update item property isDeleted to false
151
+ * @throws when element does not exist
152
+ */
153
+ public delete(key: string): void {
154
+ for (let i = 0; i < this.maxCapacity; i++) {
155
+ const index = this.hashFn(key, i);
156
+
157
+ if (this.storage[index] !== null) {
158
+ this.storage[index].isDeleted = true;
159
+ return;
160
+ }
161
+ }
162
+
163
+ throw new Error("Element does not exist");
164
+ }
165
+
166
+ /**
167
+ * Will find item in hash table
168
+ * @throws when element does not exist
169
+ */
170
+ public get(key: string): T {
171
+ return this.findNode(key).data;
172
+ }
173
+
174
+ /**
175
+ * Check if node with given key exists
176
+ */
177
+ public has(key: string): boolean {
178
+ try {
179
+ return Boolean(this.findNode(key));
180
+ } catch (e) {
181
+ return false;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Added elements count
187
+ */
188
+ public length(): number {
189
+ const actualItems = this.storage.filter((item) => {
190
+ return item !== null && !item.isDeleted;
191
+ });
192
+
193
+ return actualItems.length;
194
+ }
195
+
196
+ /**
197
+ * Will overwrite storage with array of null elements
198
+ */
199
+ public clear(): void {
200
+ this.storage = new Array(HashTable.DEFAULT_MAX_CAPACITY).fill(null);
201
+ }
202
+ }
@@ -0,0 +1,31 @@
1
+ export default class HashTableNode<T> {
2
+ private readonly _key: string;
3
+ private _data: T;
4
+ private _isDeleted: boolean;
5
+
6
+ public constructor(key: string, data: T) {
7
+ this._key = key;
8
+ this._data = data;
9
+ this._isDeleted = false;
10
+ }
11
+
12
+ get data(): T {
13
+ return this._data;
14
+ }
15
+
16
+ set data(value: T) {
17
+ this._data = value;
18
+ }
19
+
20
+ get key(): string {
21
+ return this._key;
22
+ }
23
+
24
+ get isDeleted(): boolean {
25
+ return this._isDeleted;
26
+ }
27
+
28
+ set isDeleted(value: boolean) {
29
+ this._isDeleted = value;
30
+ }
31
+ }
@@ -0,0 +1,28 @@
1
+ import HashTable from "../data-structures/HashTable/HashTable";
2
+
3
+ export const demoHashtable = (): void => {
4
+ const hashTable = new HashTable<number>(15);
5
+
6
+ // emulate 66% load
7
+ for (let i = 0; i < 10; i++) {
8
+ hashTable.set(`key${i * 2}`, i * 2);
9
+ }
10
+ console.log(hashTable);
11
+
12
+ for (let i = 10; i < 15; i++) {
13
+ hashTable.set(`key${i * 2}`, i * 2);
14
+ }
15
+ console.log(hashTable);
16
+ console.log("\n length:", hashTable.length());
17
+
18
+ hashTable.set("key4", 10000);
19
+ console.log("\n key4 value:", hashTable.get("key4"));
20
+ hashTable.delete("key4");
21
+ console.log("\n has key4:", hashTable.has("key4"));
22
+ hashTable.set("key4", 20000);
23
+ console.log("\n has key4:", hashTable.has("key4"));
24
+ console.log("\n key4 value:", hashTable.get("key4"));
25
+ console.log("\n", hashTable);
26
+ hashTable.clear();
27
+ console.log("\n", hashTable);
28
+ };
@@ -0,0 +1,40 @@
1
+ import { perf, roundNumber } from "../../utils";
2
+ import IKeyValueStorage from "../../types/IKeyValueStorage";
3
+ import HashTable from "../../data-structures/HashTable/HashTable";
4
+
5
+ export const pushToStorage = (
6
+ linearDS: IKeyValueStorage<number>,
7
+ elementsCount: number
8
+ ): void => {
9
+ for (let i = 0; i < elementsCount; i++) {
10
+ linearDS.set(`key${i}`, i);
11
+ }
12
+ };
13
+
14
+ export const perfHashTable = (): void => {
15
+ console.log(`HASHTABLE PERFORMANCE TEST:`);
16
+ const hashTable: IKeyValueStorage<number> = new HashTable<number>();
17
+ let elementsCount = 50;
18
+
19
+ while (elementsCount <= 5000000) {
20
+ pushToStorage(hashTable, elementsCount);
21
+
22
+ const perfSet = perf(() => {
23
+ hashTable.set(`key${elementsCount}`, elementsCount);
24
+ });
25
+ const perfGet = perf(() => {
26
+ hashTable.get(`key${elementsCount}`);
27
+ });
28
+
29
+ const perfHas = perf(() => {
30
+ hashTable.has(`key${elementsCount - 10}`);
31
+ });
32
+
33
+ console.log(`N: ${elementsCount} set: ${roundNumber(perfSet)}ms`);
34
+ console.log(`N: ${elementsCount} get: ${roundNumber(perfGet)}ms`);
35
+ console.log(`N: ${elementsCount} has: ${roundNumber(perfHas)}ms`);
36
+ console.log("=========================");
37
+
38
+ elementsCount *= 10;
39
+ }
40
+ };
package/src/index.ts CHANGED
@@ -1,40 +1,44 @@
1
- /**
2
- * Write your code here
3
- */
4
- // import { demoBst, demoBstObjects } from "./demo/demo.bst";
5
- // import { bstCompare } from "./demo/performance/bst-compare";
6
- // import { demoStack } from "./demo/demo.stack";
7
- // import { perfQueue, perfStack } from "./demo/performance/ds-compare";
8
- // import { demoLinkedList } from "./demo/demo.linked-list";
9
- // import { demoQueue } from "./demo/demo.queue";
10
- // import {
11
- // demoDirectedGraph,
12
- // demoGraphGenerated,
13
- // demoUndirectedGraph,
14
- // } from "./demo/demo.graph";
15
- // import { demoLoopedArray } from "./demo/demo.looped-array";
16
- // import { compareAllSortTypes } from "./demo/performance/sort-compare";
17
- //
18
- // demoLinkedList();
19
- // // console.log("================================================================");
20
- // demoLoopedArray();
21
- // // console.log("================================================================");
22
- // demoQueue();
23
- // // console.log("================================================================");
24
- // demoStack();
25
- // // console.log("================================================================");
26
- // demoUndirectedGraph();
27
- // // console.log("================================================================");
28
- // demoDirectedGraph();
29
- // // console.log("================================================================");
30
- // demoGraphGenerated();
31
- // // console.log("================================================================");
32
- // compareAllSortTypes();
33
- // // console.log("===========================================================");
34
- // perfQueue();
35
- // // console.log("===========================================================");
36
- // perfStack();
37
- // // console.log("===========================================================");
38
- // demoBst();
39
- // // console.log("===========================================================");
40
- // bstCompare();
1
+ /**
2
+ * Write your code here
3
+ */
4
+ // import { demoHashtable } from "./demo/demo.hashtable";
5
+ import { perfHashTable } from "./demo/performance/hash-table.compare";
6
+ // import { demoBst, demoBstObjects } from "./demo/demo.bst";
7
+ // import { bstCompare } from "./demo/performance/bst-compare";
8
+ // import { demoStack } from "./demo/demo.stack";
9
+ // import { perfQueue, perfStack } from "./demo/performance/ds-compare";
10
+ // import { demoLinkedList } from "./demo/demo.linked-list";
11
+ // import { demoQueue } from "./demo/demo.queue";
12
+ // import {
13
+ // demoDirectedGraph,
14
+ // demoGraphGenerated,
15
+ // demoUndirectedGraph,
16
+ // } from "./demo/demo.graph";
17
+ // import { demoLoopedArray } from "./demo/demo.looped-array";
18
+ // import { compareAllSortTypes } from "./demo/performance/sort-compare";
19
+ //
20
+ // demoLinkedList();
21
+ // // console.log("================================================================");
22
+ // demoLoopedArray();
23
+ // // console.log("================================================================");
24
+ // demoQueue();
25
+ // // console.log("================================================================");
26
+ // demoStack();
27
+ // // console.log("================================================================");
28
+ // demoUndirectedGraph();
29
+ // // console.log("================================================================");
30
+ // demoDirectedGraph();
31
+ // // console.log("================================================================");
32
+ // demoGraphGenerated();
33
+ // // console.log("================================================================");
34
+ // compareAllSortTypes();
35
+ // // console.log("===========================================================");
36
+ // perfQueue();
37
+ // // console.log("===========================================================");
38
+ // perfStack();
39
+ // // console.log("===========================================================");
40
+ // demoBst();
41
+ // // console.log("===========================================================");
42
+ // bstCompare();
43
+ // demoHashtable();
44
+ perfHashTable();
@@ -0,0 +1,8 @@
1
+ export default interface IKeyValueStorage<T> {
2
+ set(key: string, value: T): void;
3
+ has(key: string): boolean;
4
+ get(key: string): T;
5
+ delete(key: string): void;
6
+ length(): number;
7
+ clear(): void;
8
+ }
@@ -0,0 +1,176 @@
1
+ import IKeyValueStorage from "../../../../src/types/IKeyValueStorage";
2
+ import HashTable from "../../../../src/data-structures/HashTable/HashTable";
3
+
4
+ describe("Hash Table", () => {
5
+ describe("constructor", () => {
6
+ it("should throw when given capacity value is less than 1", () => {
7
+ expect(() => {
8
+ new HashTable(-5);
9
+ }).toThrowError();
10
+ });
11
+
12
+ it("should create empty instance", () => {
13
+ const hashTable = new HashTable();
14
+ expect(hashTable.length()).toBe(0);
15
+ });
16
+ });
17
+
18
+ describe("method set", () => {
19
+ it("should support multiple data types", () => {
20
+ const hashTable: IKeyValueStorage<
21
+ number | string | number[]
22
+ > = new HashTable();
23
+ hashTable.set("myNumberProp", 5);
24
+ hashTable.set("myStringProp", "string");
25
+ hashTable.set("myArrayProp", [1, 2, 3, 4, 5]);
26
+
27
+ expect(hashTable.get("myNumberProp")).toBe(5);
28
+ expect(hashTable.get("myStringProp")).toBe("string");
29
+ expect(hashTable.get("myArrayProp")).toEqual([1, 2, 3, 4, 5]);
30
+ });
31
+
32
+ it("should correctly create new prop", () => {
33
+ const hashTable: IKeyValueStorage<number> = new HashTable();
34
+ hashTable.set("key", 5);
35
+
36
+ expect(hashTable.get("key")).toBe(5);
37
+ });
38
+
39
+ it("should correctly update value when it already exists", () => {
40
+ const hashTable: IKeyValueStorage<number> = new HashTable();
41
+ hashTable.set("key", 5);
42
+ hashTable.set("key", 10);
43
+
44
+ expect(hashTable.get("key")).toBe(10);
45
+ });
46
+
47
+ it("should be possible to create new nodes after deletion", () => {
48
+ const hashTable: IKeyValueStorage<number> = new HashTable(5);
49
+ hashTable.set("key1", 1);
50
+ hashTable.set("key2", 2);
51
+ hashTable.set("key3", 3);
52
+ hashTable.delete("key2");
53
+ hashTable.delete("key3");
54
+ hashTable.set("key4", 4);
55
+
56
+ expect(hashTable.get("key4")).toBe(4);
57
+ });
58
+
59
+ it("should correctly increase hashtable size when capacity >= 50% of max capacity", () => {
60
+ const hashTable: IKeyValueStorage<number> = new HashTable(5);
61
+ for (let i = 0; i < 15; i++) {
62
+ hashTable.set(`key${i}`, i);
63
+ }
64
+
65
+ expect(hashTable.length()).toBe(15);
66
+ });
67
+ });
68
+
69
+ describe("method has", () => {
70
+ it("should return false when hashtable is empty", () => {
71
+ const hashTable: IKeyValueStorage<number> = new HashTable();
72
+
73
+ expect(hashTable.has("NOT_EXISTED_PROP")).toBe(false);
74
+ });
75
+
76
+ it("should return true when property is exists", () => {
77
+ const hashTable: IKeyValueStorage<number> = new HashTable();
78
+ hashTable.set("key", 5);
79
+
80
+ expect(hashTable.has("key")).toBe(true);
81
+ });
82
+
83
+ it("should return false after deletion", () => {
84
+ const hashTable: IKeyValueStorage<number> = new HashTable();
85
+ hashTable.set("key", 5);
86
+ hashTable.delete("key");
87
+
88
+ expect(hashTable.has("key")).toBe(false);
89
+ });
90
+ });
91
+
92
+ describe("method get", () => {
93
+ it("should throw when hashtable is empty", () => {
94
+ const hashTable: IKeyValueStorage<number> = new HashTable();
95
+
96
+ expect(() => {
97
+ hashTable.get("key");
98
+ }).toThrowError();
99
+ });
100
+
101
+ it("should return true when property is exists", () => {
102
+ const hashTable: IKeyValueStorage<number> = new HashTable();
103
+ hashTable.set("key", 5);
104
+
105
+ expect(hashTable.get("key")).toBe(5);
106
+ });
107
+
108
+ it("should throw after property deletion", () => {
109
+ const hashTable: IKeyValueStorage<number> = new HashTable();
110
+ hashTable.set("key", 5);
111
+ hashTable.delete("key");
112
+
113
+ expect(() => {
114
+ hashTable.get("key");
115
+ }).toThrowError();
116
+ });
117
+ });
118
+
119
+ describe("method delete", () => {
120
+ it("should delete prop correctly", () => {
121
+ const hashTable: IKeyValueStorage<number> = new HashTable();
122
+ hashTable.set("key", 5);
123
+ hashTable.delete("key");
124
+
125
+ expect(hashTable.has("key")).toBe(false);
126
+ });
127
+
128
+ it("should throw when element does not exist", () => {
129
+ const hashTable = new HashTable();
130
+
131
+ expect(() => {
132
+ hashTable.delete("NOT_EXISTED_PROP");
133
+ }).toThrowError();
134
+ });
135
+ });
136
+
137
+ describe("method length", () => {
138
+ it("should return 0 when table is empty", () => {
139
+ const hashTable: IKeyValueStorage<number> = new HashTable();
140
+
141
+ expect(hashTable.length()).toBe(0);
142
+ });
143
+
144
+ it("should return correct length value of added elements", () => {
145
+ const hashTable: IKeyValueStorage<number> = new HashTable();
146
+ hashTable.set("key1", 1);
147
+ hashTable.set("key2", 2);
148
+ hashTable.set("key3", 3);
149
+
150
+ expect(hashTable.length()).toBe(3);
151
+ });
152
+
153
+ it("should return updated length value after deletion", () => {
154
+ const hashTable: IKeyValueStorage<number> = new HashTable();
155
+ hashTable.set("key1", 1);
156
+ hashTable.set("key2", 2);
157
+ hashTable.set("key3", 3);
158
+ hashTable.delete("key1");
159
+
160
+ expect(hashTable.length()).toBe(2);
161
+ });
162
+ });
163
+
164
+ describe("method clear", () => {
165
+ it("should remove all elements", () => {
166
+ const hashTable: IKeyValueStorage<number> = new HashTable();
167
+ hashTable.set("key1", 1);
168
+ hashTable.set("key2", 2);
169
+ hashTable.clear();
170
+
171
+ expect(hashTable.length()).toBe(0);
172
+ expect(hashTable.has("key1")).toBe(false);
173
+ expect(hashTable.has("key2")).toBe(false);
174
+ });
175
+ });
176
+ });