1 | import { NullPointerException } from "../exception/NotNullException";
|
2 | import LinkList from "../linklist/LinkList";
|
3 | import { hash, toString } from "../util";
|
4 | function defineHashNodeToString<T>(node: {key: any, value: T}){
|
5 | Object.defineProperty(node, "toString", {
|
6 |
|
7 | value: function(){
|
8 | return JSON.stringify(this);
|
9 | },
|
10 | });
|
11 | }
|
12 |
|
13 |
|
14 |
|
15 | export class HashTable<T>{
|
16 | private buckets: Array<LinkList<{key: string, value: T}>>;
|
17 | private count: number;
|
18 | |
19 |
|
20 |
|
21 | private threshold: number;
|
22 | private keys: {[index: string]: number};
|
23 | private static DEFAULT_TABLE_SIZE = 11;
|
24 | |
25 |
|
26 |
|
27 | private static readonly LOADFACTOR = 0.75;
|
28 |
|
29 | static setDefaultTableSize(size: number){
|
30 | this.DEFAULT_TABLE_SIZE = size;
|
31 | }
|
32 |
|
33 | public get Count(){
|
34 | return this.count;
|
35 | }
|
36 |
|
37 | public get TableSize(){
|
38 | return this.buckets.length;
|
39 | }
|
40 |
|
41 | |
42 |
|
43 |
|
44 |
|
45 | constructor(size = HashTable.DEFAULT_TABLE_SIZE){
|
46 | this.buckets = Array(size).fill(null).map(() => new LinkList<{key: any, value: T}>());
|
47 | this.count = 0;
|
48 | this.keys = {};
|
49 | this.threshold = size * HashTable.LOADFACTOR;
|
50 | }
|
51 |
|
52 | |
53 |
|
54 |
|
55 |
|
56 |
|
57 | public put(key: any, value: T){
|
58 | if (key === null || key === undefined){
|
59 | throw new NullPointerException();
|
60 | }
|
61 | const tempKey = toString(key);
|
62 |
|
63 | const hashed = hash(tempKey);
|
64 | let keyHash = this.mod(hashed);
|
65 |
|
66 | let bucketLinkedList = this.buckets[keyHash];
|
67 |
|
68 | const node = bucketLinkedList.findNode(item => item.key === key);
|
69 | if (node){
|
70 | node.Value.value = value;
|
71 | return;
|
72 | }
|
73 |
|
74 | if (this.count >= this.threshold){
|
75 | this.rehash();
|
76 | keyHash = this.mod(hashed);
|
77 | bucketLinkedList = this.buckets[keyHash];
|
78 | }
|
79 | this.keys[tempKey] = keyHash;
|
80 | bucketLinkedList.append({
|
81 | key,
|
82 | value,
|
83 | });
|
84 | this.count++;
|
85 | return this;
|
86 | }
|
87 |
|
88 | |
89 |
|
90 |
|
91 |
|
92 | public get(key: any): T{
|
93 | const tempKey = toString(key);
|
94 | const hashed = hash(tempKey);
|
95 | const keyHash = this.mod(hashed);
|
96 | const bucketLinkedList = this.buckets[keyHash];
|
97 | const node = bucketLinkedList.findNode(item => item.key === key);
|
98 | return node ? node.Value.value : null;
|
99 | }
|
100 |
|
101 | |
102 |
|
103 |
|
104 |
|
105 | public remove(key: any): boolean{
|
106 | const tempKey = toString(key);
|
107 | const hashed = hash(tempKey);
|
108 | const keyHash = this.mod(hashed);
|
109 | const bucketLinkedList = this.buckets[keyHash];
|
110 | const node = bucketLinkedList.findNode(item => item.key === key);
|
111 | if (node){
|
112 | this.count--;
|
113 |
|
114 | return bucketLinkedList.deleteNode(node.Value);
|
115 | }
|
116 | return false;
|
117 | }
|
118 |
|
119 | public contains(key: any): boolean{
|
120 | return this.get(key) !== null;
|
121 | }
|
122 |
|
123 | |
124 |
|
125 |
|
126 | public getKeys(): Array<string>{
|
127 | return Object.keys(this.keys);
|
128 | }
|
129 |
|
130 | |
131 |
|
132 |
|
133 | public getOrignalKeys(): Array<any>{
|
134 | const arr = new Array<any>(this.count);
|
135 | this.iterate((item, index) => {
|
136 | arr[index] = item.key;
|
137 | });
|
138 | return arr;
|
139 | }
|
140 |
|
141 | |
142 |
|
143 |
|
144 | public values(): Array<T>{
|
145 | const arr = new Array<T>(this.count);
|
146 | this.iterate((item, index) => {
|
147 | arr[index] = item.value;
|
148 | });
|
149 | return arr;
|
150 | }
|
151 |
|
152 | |
153 |
|
154 |
|
155 | public clear(){
|
156 | this.buckets.length = 0;
|
157 | this.keys = {};
|
158 | this.count = 0;
|
159 | this.buckets = Array(HashTable.DEFAULT_TABLE_SIZE)
|
160 | .fill(null).map(() => new LinkList<{key: string, value: T}>());
|
161 | }
|
162 |
|
163 | |
164 |
|
165 |
|
166 |
|
167 | public getHashKey(key: any){
|
168 | const tempKey = toString(key);
|
169 | return this.keys[tempKey];
|
170 | }
|
171 |
|
172 | public toString(){
|
173 | const arr: Array<{key: string, value: T}> = [];
|
174 | this.iterate(item => {
|
175 | const node = {
|
176 | key: item.key,
|
177 | value: item.value,
|
178 | };
|
179 | defineHashNodeToString(node);
|
180 | arr.push(node);
|
181 | });
|
182 | return arr.toString();
|
183 | }
|
184 |
|
185 | |
186 |
|
187 |
|
188 |
|
189 | private iterate(fn: (item: {key: string, value: T}, index?: number) => void){
|
190 | let iterateFlag = 0;
|
191 |
|
192 | for (let i = 0, count = this.buckets.length; i < count ; i++){
|
193 | const linkArr = this.buckets[i].toArray();
|
194 | const arrCount = iterateFlag;
|
195 | for (let j = arrCount, addCount = arrCount + linkArr.length; j < addCount ; j++){
|
196 | fn(linkArr[j - arrCount].Value, iterateFlag);
|
197 | iterateFlag++;
|
198 | }
|
199 | }
|
200 | }
|
201 |
|
202 | |
203 |
|
204 |
|
205 | private rehash(){
|
206 | const oldBuckets = this.buckets;
|
207 |
|
208 | const newCapacity = oldBuckets.length * 2 + 1;
|
209 | const newBuckets = Array(newCapacity).fill(null).map(() => new LinkList<{key: string, value: T}>());
|
210 | this.buckets = newBuckets;
|
211 | this.keys = {};
|
212 |
|
213 | for (let i = 0 , oldLen = oldBuckets.length; i < oldLen; i++){
|
214 | oldBuckets[i].toArray().forEach(item => {
|
215 | const data = item.Value;
|
216 | const hashed = hash(toString(data.key));
|
217 | const keyHash = this.mod(hashed);
|
218 | newBuckets[keyHash].append(data);
|
219 | this.keys[toString(data.key)] = keyHash;
|
220 | });
|
221 | }
|
222 | oldBuckets.length = 0;
|
223 |
|
224 | this.threshold = newCapacity * HashTable.LOADFACTOR;
|
225 | }
|
226 |
|
227 | private mod(hashed: number){
|
228 | const modulo = hashed % this.buckets.length;
|
229 | return hashed < 0 ? modulo * -1 : modulo;
|
230 | }
|
231 | }
|