1 /** 2 Copyright: Copyright (c) 2016-2017 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.container.hashmap; 7 8 import voxelman.log; 9 import std.stdio; 10 import std..string; 11 import std.experimental.allocator.gc_allocator; 12 import voxelman.math : nextPOT; 13 14 struct HashMap(Key, Value, Key nullKey = Key.max, A = GCAllocator) 15 { 16 Key[] keys; 17 Value[] values; 18 size_t length; 19 20 private bool resizing; 21 22 alias allocator = A.instance; 23 24 this(ubyte[] array, size_t length) { 25 assert(array.length % (Key.sizeof + Value.sizeof) == 0); 26 size_t size = array.length / (Key.sizeof + Value.sizeof); 27 keys = cast(Key[])array[0..Key.sizeof * size]; 28 values = cast(Value[])array[Key.sizeof * size..$]; 29 this.length = length; 30 } 31 32 ubyte[] getTable() { 33 return (cast(ubyte[])keys).ptr[0..(Key.sizeof + Value.sizeof) * keys.length]; 34 } 35 36 @property size_t capacity() const { return keys.length; } 37 @property bool empty() const { return length == 0; } 38 39 void remove(Key key) { 40 auto idx = findIndex(key); 41 if (idx == size_t.max) return; 42 auto i = idx; 43 while (true) 44 { 45 keys[i] = nullKey; 46 47 size_t j = i, r; 48 do { 49 if (++i >= keys.length) i -= keys.length; 50 if (keys[i] == nullKey) 51 { 52 --length; 53 return; 54 } 55 r = keys[i] & (keys.length-1); 56 } 57 while ((j<r && r<=i) || (i<j && j<r) || (r<=i && i<j)); 58 keys[j] = keys[i]; 59 values[j] = values[i]; 60 } 61 } 62 63 Value get(Key key, Value default_value = Value.init) { 64 auto idx = findIndex(key); 65 if (idx == size_t.max) return default_value; 66 return values[idx]; 67 } 68 69 Value* getOrCreate(Key key, Value defVal = Value.init) { 70 auto idx = findIndex(key); 71 if (idx == size_t.max) return assignValue(key, defVal); 72 return &values[idx]; 73 } 74 75 void clear() { 76 keys[] = nullKey; 77 length = 0; 78 } 79 80 void opIndexAssign(Value value, Key key) { 81 assignValue(key, value); 82 } 83 84 private Value* assignValue(Key key, Value value) { 85 grow(1); 86 auto i = findInsertIndex(key); 87 if (keys[i] != key) ++length; 88 89 keys[i] = key; 90 values[i] = value; 91 return &values[i]; 92 } 93 94 ref inout(Value) opIndex(Key key) inout { 95 auto idx = findIndex(key); 96 assert (idx != size_t.max, "Accessing non-existent key."); 97 return values[idx]; 98 } 99 100 inout(Value)* opBinaryRight(string op)(Key key) inout if (op == "in") { 101 auto idx = findIndex(key); 102 if (idx == size_t.max) return null; 103 return &values[idx]; 104 } 105 106 int opApply(scope int delegate(ref Value) del) { 107 foreach (i; 0 .. keys.length) 108 if (keys[i] != nullKey) 109 if (auto ret = del(values[i])) 110 return ret; 111 return 0; 112 } 113 114 int opApply(scope int delegate(in ref Value) del) const { 115 foreach (i; 0 .. keys.length) 116 if (keys[i] != nullKey) 117 if (auto ret = del(values[i])) 118 return ret; 119 return 0; 120 } 121 122 int opApply(scope int delegate(Key, ref Value) del) { 123 foreach (i; 0 .. keys.length) 124 if (keys[i] != nullKey) 125 if (auto ret = del(keys[i], values[i])) 126 return ret; 127 return 0; 128 } 129 130 int opApply(scope int delegate(in ref Key, in ref Value) del) const { 131 foreach (i; 0 .. keys.length) 132 if (keys[i] != nullKey) 133 if (auto ret = del(keys[i], values[i])) 134 return ret; 135 return 0; 136 } 137 138 void reserve(size_t amount) { 139 auto newcap = ((length + amount) * 3) / 2; 140 resize(newcap); 141 } 142 143 void shrink() { 144 auto newcap = length * 3 / 2; 145 resize(newcap); 146 } 147 148 void printStats() { 149 writefln("cap %s len %s", capacity, length); 150 } 151 152 private size_t findIndex(Key key) const { 153 if (length == 0) return size_t.max; 154 size_t start = key & (keys.length-1); 155 auto i = start; 156 while (keys[i] != key) { 157 if (keys[i] == nullKey) return size_t.max; 158 if (++i >= keys.length) i -= keys.length; 159 if (i == start) return size_t.max; 160 } 161 return i; 162 } 163 164 private size_t findInsertIndex(Key key) const { 165 size_t target = key & (keys.length-1); 166 auto i = target; 167 while (keys[i] != nullKey && keys[i] != key) { 168 if (++i >= keys.length) i -= keys.length; 169 assert (i != target, "No free bucket found, HashMap full!?"); 170 } 171 return i; 172 } 173 174 private void grow(size_t amount) { 175 auto newsize = length + amount; 176 if (newsize < (keys.length*2)/3) return; 177 auto newcap = keys.length ? keys.length : 1; 178 while (newsize >= (newcap*2)/3) newcap *= 2; 179 resize(newcap); 180 } 181 182 private void resize(size_t newSize) 183 { 184 assert(!resizing); 185 resizing = true; 186 scope(exit) resizing = false; 187 188 newSize = nextPOT(newSize); 189 190 auto oldKeys = keys; 191 auto oldValues = values; 192 193 if (newSize) { 194 void[] array = allocator.allocate((Key.sizeof + Value.sizeof) * newSize); 195 keys = cast(Key[])(array[0..Key.sizeof * newSize]); 196 values = cast(Value[])(array[Key.sizeof * newSize..$]); 197 //infof("%s %s %s", array.length, keys.length, values.length); 198 keys[] = nullKey; 199 foreach (i, ref key; oldKeys) { 200 if (key != nullKey) { 201 auto idx = findInsertIndex(key); 202 keys[idx] = key; 203 values[idx] = oldValues[i]; 204 } 205 } 206 } else { 207 keys = null; 208 values = null; 209 } 210 211 if (oldKeys) { 212 void[] arr = (cast(void[])oldKeys).ptr[0..(Key.sizeof + Value.sizeof) * newSize]; 213 allocator.deallocate(arr); 214 } 215 } 216 217 void toString()(scope void delegate(const(char)[]) sink) 218 { 219 import std.format : formattedWrite; 220 sink.formattedWrite("[",); 221 foreach(key, value; this) 222 { 223 sink.formattedWrite("%s:%s, ", key, value); 224 } 225 sink.formattedWrite("]"); 226 } 227 } 228 229 /* 230 unittest { 231 BlockEntityMap map; 232 233 foreach (ushort i; 0 .. 100) { 234 map[i] = i; 235 assert(map.length == i+1); 236 } 237 map.printStats(); 238 239 foreach (ushort i; 0 .. 100) { 240 auto pe = i in map; 241 assert(pe !is null && *pe == i); 242 assert(map[i] == i); 243 } 244 map.printStats(); 245 246 foreach (ushort i; 0 .. 50) { 247 map.remove(i); 248 assert(map.length == 100-i-1); 249 } 250 map.shrink(); 251 map.printStats(); 252 253 foreach (ushort i; 50 .. 100) { 254 auto pe = i in map; 255 assert(pe !is null && *pe == i); 256 assert(map[i] == i); 257 } 258 map.printStats(); 259 260 foreach (ushort i; 50 .. 100) { 261 map.remove(i); 262 assert(map.length == 100-i-1); 263 } 264 map.printStats(); 265 map.shrink(); 266 map.printStats(); 267 map.reserve(100); 268 map.printStats(); 269 }*/ 270 271 unittest { 272 ushort[] keys = [140,268,396,524,652,780,908,28,156,284, 273 412,540,668,796,924,920,792,664,536,408,280,152,24]; 274 HashMap!(ushort, ulong) map; 275 276 foreach (i, ushort key; keys) { 277 //writefln("set1 %s %s", map, map.length); 278 map[key] = key; 279 //writefln("set2 %s %s", map, map.length); 280 assert(map.length == i+1, format("%s %s", i+1, map.length)); 281 assert(key in map && map[key] == key); 282 assert(map.get(key, 0) == key); 283 } 284 }