-
Notifications
You must be signed in to change notification settings - Fork 73
/
Copy pathclass_HashTable_small.ahk
289 lines (279 loc) · 8.54 KB
/
class_HashTable_small.ahk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
class HashTable
{
__New(Items*)
{
local
; An AutoHotkey Array takes the place of the array that would normally
; be used to implement a hash table's buckets.
;
; Masking to remove the unwanted high bits to fit within the array
; bounds is unnecessary because AutoHotkey Arrays are sparse arrays that
; support negative indexes.
;
; Rehashing everything and placing it in a new array that has the next
; highest power of 2 elements when over 3/4ths of the buckets are full
; is unnecessary for the same reason.
this._Buckets := []
this._Count := 0
loop % Items.Length()
{
if (not Items.HasKey(a_index))
{
throw Exception("Missing Argument Error", -1
,"HashTable.__New(Items*)")
}
if (not ( IsObject(Items[a_index])
and Items[a_index].HasKey("HasKey") != ""))
{
throw Exception("Type Error", -1
,"HashTable.__New(Items*) Invalid argument.")
}
if (not ( Items[a_index].HasKey(1)
and Items[a_index].HasKey(2)
and Items[a_index].Count() == 2))
{
throw Exception("Value Error", -1
,"HashTable.__New(Items*) Invalid argument.")
}
this.Set(Items[a_index][1], Items[a_index][2])
}
return this
}
_GetHash(Key)
{
; _GetHash(Key) is used to find the bucket a key would be stored in.
local
if (IsObject(Key))
{
HashCode := &Key
}
else
{
if Key is integer
{
HashCode := Key
}
else if Key is float
{
TruncatedKey := Key & -1
if (Key == TruncatedKey)
{
HashCode := TruncatedKey
}
else
{
; This reinterpret casts a floating point number to an
; integer with the same bitwise representation.
;
; Removing the first step will result in warnings about
; reading an uninitialized variable if warnings are turned
; on.
VarSetCapacity(HashCode, 8)
NumPut(Key, HashCode,, "Double")
HashCode := NumGet(HashCode,, "Int64")
}
}
else
{
; This is the string hashing algorithm used in Java. It makes
; use of modular arithmetic via integer overflow.
HashCode := 0
for _, Char in StrSplit(Key)
{
HashCode := 31 * HashCode + Ord(Char)
}
}
}
return HashCode
}
HasKey(Key, _HashCode := "")
{
; The _HashCode optional parameter is used to avoid repeated hashing.
local
; HasKey(Key) has the side effect of moving whatever was searched for to
; the start of the bucket's chain if it is not already there. This
; speeds future lookups.
;
; Set(Key, Value) and Get(Key) require almost identical logic, so they
; use HasKey(Key) internally to avoid code duplication. They depend on
; the chain reordering behavior.
Found := false
HashCode := _HashCode == "" ? this._GetHash(Key) : _HashCode
Item := this._Buckets.HasKey(HashCode) ? this._Buckets[HashCode]
: ""
PreviousItem := ""
while (not Found and Item != "")
{
if (Item.Key == Key)
{
if (PreviousItem != "")
{
PreviousItem.Next := Item.Next
Item.Next := this._Buckets[HashCode]
this._Buckets[HashCode] := Item
}
Found := true
}
else
{
PreviousItem := Item
Item := Item.Next
}
}
return Found
}
Set(Key, Value)
{
local
HashCode := this._GetHash(Key)
if (this.HasKey(Key, HashCode))
{
this._Buckets[HashCode].Value := Value
}
else
{
Next := this._Buckets.HasKey(HashCode) ? this._Buckets[HashCode]
: ""
this._Buckets[HashCode] := {"Key": Key
,"Value": Value
,"Next": Next}
this._Count += 1
}
return Value
}
Get(Key)
{
local
HashCode := this._GetHash(Key)
if (this.HasKey(Key, HashCode))
{
Value := this._Buckets[HashCode].Value
}
else
{
throw Exception("Key Error", -1
,"HashTable.Get(Key) Key not found.")
}
return Value
}
Delete(Key)
{
local
; Delete(Key) does not use HasKey(Key) internally because only the
; chain traversal logic is the same and reordering the chain would
; waste time.
Found := false
HashCode := this._GetHash(Key)
Item := this._Buckets.HasKey(HashCode) ? this._Buckets[HashCode]
: ""
PreviousItem := ""
while (not Found)
{
if (Item == "")
{
throw Exception("Key Error", -1
,"HashTable.Delete(Key) Key not found.")
}
if (Item.Key == Key)
{
if (PreviousItem == "")
{
if (Item.Next == "")
{
this._Buckets.Delete(HashCode)
}
else
{
this._Buckets[HashCode] := Item.Next
}
}
else
{
PreviousItem.Next := Item.Next
}
this._Count -= 1
Value := Item.Value
Found := true
}
else
{
PreviousItem := Item
Item := Item.Next
}
}
return Value
}
Count()
{
local
return this._Count
}
class _Enum
{
__New(HashTable)
{
local
; The _BucketsEnum and _PreviousItem properties are used to store
; the stopping place across calls to Next(byref Key, byref Value).
this._BucketsEnum := HashTable._Buckets._NewEnum()
this._PreviousItem := ""
return this
}
Next(byref Key, byref Value)
{
local
if (this._PreviousItem == "" or this._PreviousItem.Next == "")
{
Result := this._BucketsEnum.Next(Garbage, Item)
}
else
{
Item := this._PreviousItem.Next
Result := true
}
if (Result)
{
Key := Item.Key
Value := Item.Value
this._PreviousItem := Item
}
return Result
}
}
_NewEnum()
{
local
global HashTable
return new HashTable._Enum(this)
}
Clone()
{
local
global HashTable
Clone := new HashTable()
; Avoid rehashing when cloning.
for HashCode, Item in this._Buckets
{
PreviousCloneItem := ""
while (Item != "")
{
CloneItem := {"Key": "", "Value": "", "Next": ""}
CloneItem.Key := Item.Key
CloneItem.Value := Item.Value
if (PreviousCloneItem == "")
{
Chain := CloneItem
}
else
{
PreviousCloneItem.Next := CloneItem
}
PreviousCloneItem := CloneItem
Item := Item.Next
}
Clone._Buckets[HashCode] := Chain
}
Clone._Count := this._Count
return Clone
}
}