1 module zua.vm.hashmap; 2 import zua.vm.engine; 3 import std.typecons; 4 5 private const double LOAD_FACTOR = 0.75; 6 7 private struct Bucket { 8 size_t hash; 9 Value key; 10 Value value; 11 } 12 13 pragma(inline) size_t getHash(T)(T obj) { 14 size_t res = hashOf(obj); 15 if (res == 0) return 1; // shim to allow us to use a hash of 0 to store an empty bucket 16 return res; 17 } 18 19 /** HashTable iterator */ 20 struct HashTableIterator { 21 22 private HashTable table; 23 package size_t index; 24 25 private void skipNulls() { 26 Bucket* bucket = &table.buckets[index]; 27 while (bucket.hash == 0 || bucket.value.isNil) { 28 index++; 29 if (index >= table.buckets.length) return; 30 bucket = &table.buckets[index]; 31 } 32 } 33 34 /** Check if range is empty */ 35 bool empty() { 36 return index >= table.buckets.length; 37 } 38 39 /** Get the front element of this range */ 40 Tuple!(Value, Value) front() { 41 Bucket* bucket = &table.buckets[index]; 42 return tuple(bucket.key, bucket.value); 43 } 44 45 /** Pop the front element of this range */ 46 void popFront() { 47 index++; 48 if (index >= table.buckets.length) return; 49 skipNulls(); 50 } 51 52 } 53 54 // package struct HashTable { 55 56 // Value[Value] table; 57 58 // void initObj() { 59 60 // } 61 62 // size_t length() { 63 // return table.length; 64 // } 65 66 // /** Insert a value at a given key */ 67 // void insert(Value key, Value value) { 68 // table[key] = value; 69 // } 70 71 // /** Lookup a value from a key */ 72 // Value* lookup(Value key) { 73 // return key in table; 74 // } 75 76 // } 77 78 /** HashMap implementation for Lua tables */ 79 package class HashTable { 80 81 /** Get the number of key-value pairs in this map */ 82 size_t length; 83 84 private Bucket[] buckets; 85 86 private size_t capacityMask; 87 88 /** Create a new hashmap */ 89 this() { 90 buckets = newBuckets(16); 91 capacityMask = buckets.length - 1; 92 } 93 94 private pragma(inline) Bucket[] newBuckets(size_t numOfBuckets) { 95 return new Bucket[numOfBuckets]; 96 } 97 98 private void resize() { 99 Bucket[] prevBuckets = buckets; 100 const numBuckets = buckets.length * 2; 101 capacityMask = numBuckets - 1; 102 buckets = newBuckets(numBuckets); 103 104 length = 0; 105 106 size_t newLength; 107 foreach (i; 0 .. numBuckets / 2) { 108 Bucket* bucket = &prevBuckets[i]; 109 if (bucket.hash != 0 && !bucket.value.isNil) { 110 newLength++; 111 insert(bucket.key, bucket.value); 112 } 113 } 114 115 length = newLength; 116 } 117 118 private size_t probe(Value key, size_t hash = 0) { 119 if (hash == 0) hash = getHash(key); 120 size_t index = hash & capacityMask; 121 Nullable!size_t tombstone; 122 while (true) { 123 Bucket* bucket = &buckets[index]; 124 125 if (bucket.hash == hash && bucket.key == key) return index; 126 127 if (bucket.hash == 0) { 128 if (!tombstone.isNull) return tombstone.get; 129 return index; 130 } 131 132 if (bucket.value.isNil) tombstone = index.nullable; 133 134 index = (index + 1) & capacityMask; 135 } 136 } 137 138 /** Insert a value at a given key */ 139 void insert(Value key, Value value) { 140 if (length + 1 > LOAD_FACTOR * buckets.length) resize(); 141 142 const size_t hash = getHash(key); 143 const size_t index = probe(key, hash); 144 145 Bucket newBucket; 146 newBucket.hash = hash; 147 newBucket.key = key; 148 newBucket.value = value; 149 150 if (buckets[index].hash == 0) { 151 length++; 152 } 153 154 buckets[index] = newBucket; 155 } 156 157 /** Lookup a value from a key */ 158 Value* lookup(Value key) { 159 if (length == 0) return null; 160 161 const size_t index = probe(key); 162 163 if (buckets[index].hash == 0 || buckets[index].value.isNil) { 164 return null; 165 } 166 else { 167 return &buckets[index].value; 168 } 169 } 170 171 /** Return an iterator for this table */ 172 HashTableIterator iterator() { 173 HashTableIterator res; 174 res.index = 0; 175 res.table = this; 176 res.skipNulls(); 177 return res; 178 } 179 180 /** Return an iterator starting at, and including, a given key */ 181 HashTableIterator find(Value key) { 182 const size_t index = probe(key); 183 184 HashTableIterator res; 185 res.index = index; 186 res.table = this; 187 return res; 188 } 189 190 } 191 192 unittest { 193 HashTable table = new HashTable; 194 195 assert(table.length == 0); 196 table.insert(Value(2), Value("hi")); 197 198 auto hi = table.lookup(Value(2)); 199 assert(hi); 200 assert(*hi == Value("hi")); 201 202 foreach (d; 0 .. 100) { 203 table.insert(Value(d), Value(d * 2 + 3)); 204 } 205 206 foreach (d; 0 .. 100) { 207 auto v = table.lookup(Value(d)); 208 assert(v); 209 assert(*v == Value(d * 2 + 3)); 210 } 211 212 HashTable table2 = new HashTable; 213 214 foreach (d; 0 .. 10_000) { 215 table2.insert(Value(d + 1.25), Value(d * 2 + 3)); 216 } 217 218 foreach (d; 0 .. 10_000) { 219 auto v = table2.lookup(Value(d + 1.25)); 220 assert(v); 221 assert(*v == Value(d * 2 + 3)); 222 } 223 224 }