-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHamt.cs
342 lines (252 loc) · 11.6 KB
/
Hamt.cs
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
using System;
using System.Collections.Generic;
namespace HAMT
{
class Hamt<TKey, TValue>
{
private const int shiftAmount = 5;
private const int maxChildrenPerNode = (1 << shiftAmount);
private const int freeTableCount = maxChildrenPerNode + 1;
private const int freeTableSize = 32;
private const int maskAmount = maxChildrenPerNode - 1;
private const int emptyArrayStore = -1;
private const int startingLevelCount = (32 + shiftAmount - 1) / shiftAmount;
private readonly int[] parallelArrayIndicies = new int[freeTableCount];
private readonly INode[][] nodeArrayStore = new INode[freeTableCount * freeTableSize][];
public int ItemCount { get; private set; }
private LinkerNode root;
private IEqualityComparer<TKey> comparer;
public Hamt() : this(null) { }
public Hamt(IEqualityComparer<TKey> comparer)
{
Clear();
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
}
public void Clear()
{
root = new LinkerNode(maxChildrenPerNode, startingLevelCount);
ItemCount = 0;
for (int i = 0; i < freeTableCount; i++)
{
parallelArrayIndicies[i] = emptyArrayStore;
}
}
public void Add(TKey key, TValue value)
{
int rootShiftAmount = (shiftAmount * (startingLevelCount + 1 - root.Bitmap));
int rootCount = (1 << rootShiftAmount);
int newKeyBitGroup = key.GetHashCode();
int newKeyBitIndex = newKeyBitGroup & (rootCount - 1);
KeyValueNode newKeyValuePair = new KeyValueNode(key, value);
LinkerNode linkerNode = root.Nodes[newKeyBitIndex] as LinkerNode;
if (linkerNode != null)
{
newKeyBitGroup >>= rootShiftAmount;
for (int cLevel = 1; cLevel <= root.Bitmap; newKeyBitGroup >>= shiftAmount, cLevel++)
{
newKeyBitIndex = newKeyBitGroup & maskAmount;
int indexGivenBitIndex = GetElementCountBeforeBitIndex(newKeyBitIndex, linkerNode.Bitmap);
if (IsBitSet(newKeyBitIndex, linkerNode.Bitmap)) // Bitmap of -1 is probabilistically low with root resizing
{
KeyValueNode nextNodeKey = linkerNode.Nodes[indexGivenBitIndex] as KeyValueNode;
if (nextNodeKey != null)
{
newKeyBitGroup >>= shiftAmount;
ReplaceKeyWithLinkingNode(linkerNode.Nodes, indexGivenBitIndex, nextNodeKey, newKeyValuePair, newKeyBitGroup, newKeyBitIndex, cLevel);
return;
}
else
{
linkerNode = (LinkerNode)linkerNode.Nodes[indexGivenBitIndex];
}
}
else
{
InsertIntoLinkerNode(linkerNode, newKeyValuePair, indexGivenBitIndex, newKeyBitIndex);
return;
}
}
AppendLeavesBucket(linkerNode, newKeyValuePair);
return;
}
KeyValueNode keyValueNode = root.Nodes[newKeyBitIndex] as KeyValueNode;
if (keyValueNode != null)
{
newKeyBitGroup >>= rootShiftAmount;
ReplaceKeyWithLinkingNode(root.Nodes, newKeyBitIndex, keyValueNode, newKeyValuePair, newKeyBitGroup, newKeyBitIndex, 0);
}
else
{
root.Nodes[newKeyBitIndex] = newKeyValuePair;
ItemCount++;
}
}
private void StoreArray(INode[] arrayToCache)
{
int length = arrayToCache.Length;
if (parallelArrayIndicies[length] < freeTableSize - 1)
{
nodeArrayStore[length * freeTableSize + parallelArrayIndicies[length]++ + 1] = arrayToCache;
}
}
private INode[] TryToGetArrayFromStore(int length)
{
return parallelArrayIndicies[length] == emptyArrayStore ? new INode[length] : nodeArrayStore[length * freeTableSize + parallelArrayIndicies[length]--];
}
private void InsertIntoLinkerNode(LinkerNode linkerNode, KeyValueNode newKeyValuePair, int index, int addToBitmap)
{
int length = linkerNode.Nodes.Length;
StoreArray(linkerNode.Nodes);
INode[] newChildrenArray = TryToGetArrayFromStore(length + 1);
Array.Copy(linkerNode.Nodes, 0, newChildrenArray, 0, index);
Array.Copy(linkerNode.Nodes, index, newChildrenArray, index + 1, newChildrenArray.Length - index - 1);
newChildrenArray[index] = newKeyValuePair;
linkerNode.Bitmap |= (1 << addToBitmap);
ItemCount++;
linkerNode.Nodes = newChildrenArray;
}
private void ReplaceKeyWithLinkingNode(INode[] pNode, int pIndex, KeyValueNode oldKeyValuePair, KeyValueNode newKeyValuePair, int newKeyBitGroup, int newKeyBitIndex, int currentLevel)
{
int rootBitmap = root.Bitmap;
int rootShiftAmount = (shiftAmount * (startingLevelCount + 1 - rootBitmap));
int oldKeyBitGroup = oldKeyValuePair.Key.GetHashCode() >> (rootShiftAmount + currentLevel * shiftAmount);
int oldKeyBitIndex = oldKeyBitGroup & maskAmount;
newKeyBitIndex = newKeyBitGroup & maskAmount;
while (newKeyBitIndex == oldKeyBitIndex)
{
if (currentLevel < rootBitmap)
{
LinkerNode newChild = new LinkerNode(1, 1 << oldKeyBitIndex);
pNode[pIndex] = newChild;
pNode = newChild.Nodes;
pIndex = 0;
}
else // Collision detected so insert at leaves bucket
{
if (comparer.Equals(newKeyValuePair.Key, oldKeyValuePair.Key))
{
throw new Exception("Key already exists.");
}
ItemCount++;
const int completeBitmap = -1;
LinkerNode newLeaf = new LinkerNode(2, completeBitmap);
newLeaf.Nodes[0] = oldKeyValuePair;
newLeaf.Nodes[1] = newKeyValuePair;
pNode[pIndex] = newLeaf;
return;
}
oldKeyBitGroup >>= shiftAmount;
newKeyBitGroup >>= shiftAmount;
oldKeyBitIndex = oldKeyBitGroup & maskAmount;
newKeyBitIndex = newKeyBitGroup & maskAmount;
currentLevel++;
}
// Promote Key to LinkingNode
LinkerNode replacementNode = new LinkerNode(2, (1 << oldKeyBitIndex) | (1 << newKeyBitIndex));
const int signShiftLocation = 31;
int insertionLocation = ((oldKeyBitIndex - newKeyBitIndex) >> signShiftLocation) + 1;
replacementNode.Nodes[insertionLocation] = oldKeyValuePair;
replacementNode.Nodes[1 - insertionLocation] = newKeyValuePair;
ItemCount++;
pNode[pIndex] = replacementNode;
// Can check here whether or not root has keys. If not, resize? Can keep a running counter here as well.
if (ItemCount >= root.Nodes.Length * maxChildrenPerNode)
{
ResizeRoot();
}
}
private void ResizeRoot()
{
LinkerNode newRoot = new LinkerNode(root.Nodes.Length * maxChildrenPerNode, root.Bitmap - 1);
int rootShiftAmount = (shiftAmount * (startingLevelCount + 1 - root.Bitmap));
for (int i = 0; i < root.Nodes.Length; i++)
{
LinkerNode currentRootChild = (LinkerNode)root.Nodes[i];
int currElementIndex = 0;
for (int j = 0; j < maxChildrenPerNode; j++)
{
if (((currentRootChild.Bitmap >> j) & 1) == 1)
{
INode currentNode = currentRootChild.Nodes[currElementIndex++];
newRoot.Nodes[(j << rootShiftAmount) | i] = currentNode;
}
}
}
root = newRoot;
}
// Doesn't really matter how efficient this is because it's run so rarely.
private void AppendLeavesBucket(LinkerNode leaves, KeyValueNode newKeyValuePair)
{
INode[] newArray = new INode[leaves.Nodes.Length + 1];
Array.Copy(leaves.Nodes, newArray, leaves.Nodes.Length);
newArray[leaves.Nodes.Length] = newKeyValuePair;
leaves.Nodes = newArray;
ItemCount++;
}
public bool Contains(TKey key)
{
return Get(key) != null;
}
public KeyValueNode Get(TKey key)
{
int rootShiftAmount = (shiftAmount * (startingLevelCount + 1 - root.Bitmap));
int currBitGroup = key.GetHashCode();
int rootBitIndex = currBitGroup & (root.Nodes.Length - 1);
LinkerNode linkerNode = root.Nodes[rootBitIndex] as LinkerNode;
if (linkerNode != null)
{
currBitGroup >>= rootShiftAmount;
for (int currentLevel = 1; currentLevel <= root.Bitmap; currBitGroup >>= shiftAmount, currentLevel++)
{
int indexGivenBitIndex = GetElementCountBeforeBitIndex(currBitGroup & maskAmount, linkerNode.Bitmap);
KeyValueNode keyValueNode = linkerNode.Nodes[indexGivenBitIndex] as KeyValueNode;
if (keyValueNode != null)
{
return comparer.Equals(keyValueNode.Key, key) ? keyValueNode : null;
}
else
{
linkerNode = (LinkerNode)linkerNode.Nodes[indexGivenBitIndex];
}
}
foreach (KeyValueNode keyValueNode in linkerNode.Nodes) // Leaves
{
if (comparer.Equals(keyValueNode.Key, key))
{
return keyValueNode;
}
}
return null;
}
else
{
return (KeyValueNode)root.Nodes[rootBitIndex];
}
}
public interface INode { }
internal class LinkerNode : INode
{
public int Bitmap;
public INode[] Nodes;
public LinkerNode(int size, int bitmap) { Nodes = new INode[size]; Bitmap = bitmap; }
}
internal class KeyValueNode : INode
{
public TKey Key;
public TValue Value;
public KeyValueNode(TKey key, TValue value) { Key = key; Value = value; }
}
private static int GetElementCountBeforeBitIndex(int bitIndex, int bitMap)
{
int bitsToCheck = bitMap << (maxChildrenPerNode - bitIndex - 1) << 1;
// Start of Popcount operation
bitsToCheck = bitsToCheck - ((bitsToCheck >> 1) & 0x55555555);
bitsToCheck = (bitsToCheck & 0x33333333) + ((bitsToCheck >> 2) & 0x33333333);
return (((bitsToCheck + (bitsToCheck >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
private static bool IsBitSet(int pos, int bitMap)
{
return (bitMap & (1 << pos)) != 0;
}
}
}