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