1 /** 2 Internal hash map implementation. 3 4 Copyright: © 2013 RejectedSoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE file. 6 Authors: Sönke Ludwig 7 */ 8 module memutils.hashmap; 9 10 import std.conv : emplace, to; 11 import std.traits; 12 import std.algorithm : countUntil; 13 import memutils.refcounted; 14 import memutils.constants; 15 import memutils.helpers; 16 import memutils.allocators; 17 import memutils.utils; 18 19 20 alias HashMapRef(Key, Value, ALLOC = ThreadMem) = RefCounted!(HashMap!(Key, Value, ALLOC), ALLOC); 21 22 struct HashMap(Key, Value, ALLOC = ThreadMem) 23 { 24 @disable this(this); 25 26 alias Traits = DefaultHashMapTraits!Key; 27 struct TableEntry { 28 UnConst!Key key; 29 Value value; 30 31 this(Key key, Value value) { this.key = cast(UnConst!Key)key; this.value = value; } 32 } 33 private { 34 TableEntry[] m_table; // NOTE: capacity is always POT 35 size_t m_length; 36 hash_t delegate(Key) m_hasher; 37 bool m_resizing; 38 } 39 40 nothrow ~this() 41 { 42 try { 43 clear(); 44 if (m_table) freeArray!(TableEntry, ALLOC)(m_table); 45 } catch(Throwable e) { assert(false, e.toString()); } 46 } 47 48 @property bool empty() const { return length == 0; } 49 @property size_t length() const { return m_length; } 50 51 void remove(Key key) 52 { 53 //static if (Key.stringof.countUntil("OIDImpl") != -1) logError("Removing: ", key.toString()); 54 auto idx = findIndex(key); 55 assert (idx != size_t.max, "Removing non-existent element."); 56 auto i = idx; 57 while (true) { 58 m_table[i].key = Traits.clearValue; 59 m_table[i].value = Value.init; 60 size_t j = i, r; 61 do { 62 if (++i >= m_table.length) i -= m_table.length; 63 if (Traits.equals(m_table[i].key, Traits.clearValue)) { 64 m_length--; 65 return; 66 } 67 r = m_hasher(m_table[i].key) & (m_table.length-1); 68 69 } while ((j<r && r<=i) || (i<j && j<r) || (r<=i && i<j)); 70 m_table[j] = m_table[i]; 71 } 72 } 73 74 Value get(in Key key, lazy Value default_value = Value.init) const 75 { 76 auto idx = this.findIndex(key); 77 if (idx == size_t.max) return default_value; 78 const Value ret = m_table[idx].value; 79 return *cast(Value*)&ret; 80 } 81 /* 82 static if (!is(typeof({ Value v; const(Value) vc; v = vc; }))) { 83 const(Value) get(Key key, lazy const(Value) default_value = Value.init) 84 { 85 auto idx = findIndex(key); 86 if (idx == size_t.max) return default_value; 87 return m_table[idx].value; 88 } 89 }*/ 90 91 void clear() 92 { 93 foreach (i; 0 .. m_table.length) 94 if (!Traits.equals(m_table[i].key, Traits.clearValue)) { 95 m_table[i].key = Traits.clearValue; 96 m_table[i].value = Value.init; 97 } 98 m_length = 0; 99 } 100 101 void set(Key key, Value value) { 102 opIndexAssign(value, key); 103 } 104 105 106 void opIndexAssign(inout Value value, in Key key) 107 { 108 //assert(!Traits.equals(key, Traits.clearValue), "Inserting clear value into hash map."); 109 grow(1); 110 auto i = findInsertIndex(key); 111 if (!Traits.equals(m_table[i].key, key)) m_length++; 112 m_table[i] = TableEntry(*cast(Key*) &key, *cast(Value*) &value); 113 } 114 115 ref inout(Value) opIndex(Key key) inout { 116 auto idx = findIndex(key); 117 assert (idx != size_t.max, "Accessing non-existent key type: " ~ Key.stringof ~ " value: " ~ key.to!string); 118 return m_table[idx].value; 119 } 120 121 Value opIndex(Key key) const { 122 auto idx = findIndex(key); 123 assert (idx != size_t.max, "Accessing non-existent key type: " ~ Key.stringof ~ " value: " ~ key.to!string); 124 const Value ret = m_table[idx].value; 125 return *cast(Value*) &ret; 126 } 127 128 inout(Value)* opBinaryRight(string op)(Key key) 129 inout if (op == "in") { 130 auto idx = findIndex(key); 131 if (idx == size_t.max) return null; 132 return &m_table[idx].value; 133 } 134 135 int opApply(int delegate(in ref Value) del) 136 const { 137 foreach (i; 0 .. m_table.length) 138 if (!Traits.equals(m_table[i].key, Traits.clearValue)) 139 if (auto ret = del(m_table[i].value)) 140 return ret; 141 return 0; 142 } 143 144 int opApply(int delegate(ref Value) del) 145 { 146 foreach (i; 0 .. m_table.length) 147 if (!Traits.equals(m_table[i].key, Traits.clearValue)) 148 if (auto ret = del(m_table[i].value)) 149 return ret; 150 return 0; 151 } 152 153 int opApply(int delegate(in ref Key, in ref Value) del) 154 const { 155 foreach (i; 0 .. m_table.length) 156 if (!Traits.equals(m_table[i].key, Traits.clearValue)) 157 if (auto ret = del(m_table[i].key, m_table[i].value)) 158 return ret; 159 return 0; 160 } 161 162 int opApply(int delegate(in ref Key, in ref Value) del) 163 { 164 foreach (i; 0 .. m_table.length) 165 if (!Traits.equals(m_table[i].key, Traits.clearValue)) 166 if (auto ret = del(m_table[i].key, m_table[i].value)) 167 return ret; 168 return 0; 169 } 170 171 int opApply(int delegate(in ref Key, ref Value) del) 172 { 173 foreach (i; 0 .. m_table.length) 174 if (!Traits.equals(m_table[i].key, Traits.clearValue)) 175 if (auto ret = del(m_table[i].key, m_table[i].value)) 176 return ret; 177 return 0; 178 } 179 180 private size_t findIndex(in Key key) 181 const { 182 if (m_length == 0) return size_t.max; 183 size_t start = m_hasher(*cast(Key*) &key) & (m_table.length-1); 184 auto i = start; 185 while (!Traits.equals(m_table[i].key, key)) { 186 if (Traits.equals(m_table[i].key, Traits.clearValue)) return size_t.max; 187 if (++i >= m_table.length) i -= m_table.length; 188 if (i == start) return size_t.max; 189 } 190 return i; 191 } 192 193 private size_t findInsertIndex(in Key key) 194 const { 195 auto hash = m_hasher(*cast(Key*) &key); 196 size_t target = hash & (m_table.length-1); 197 auto i = target; 198 while (!Traits.equals(m_table[i].key, Traits.clearValue) && !Traits.equals(m_table[i].key, key)) { 199 if (++i >= m_table.length) i -= m_table.length; 200 assert (i != target, "No free bucket found, HashMap full!?"); 201 } 202 return i; 203 } 204 205 private void grow(size_t amount) 206 { 207 auto newsize = m_length + amount; 208 if (newsize < (m_table.length*2)/3) return; 209 auto newcap = m_table.length ? m_table.length : 16; 210 while (newsize >= (newcap*2)/3) newcap *= 2; 211 resize(newcap); 212 } 213 214 private void resize(size_t new_size) 215 { 216 assert(!m_resizing); 217 m_resizing = true; 218 scope(exit) m_resizing = false; 219 220 if (!m_hasher) { 221 setupHasher(); 222 } 223 224 // logTrace("Resizing ", Key.stringof, " : ", Value.stringof, " : ", cast(void*)&this, " from ", m_table.length, " to: ", new_size); 225 226 uint pot = 0; 227 while (new_size > 1) pot++, new_size /= 2; 228 new_size = 1 << pot; 229 auto old_size = m_table.length; 230 assert(new_size > old_size); // TODO: Allow hashmap to become smaller? 231 auto oldtable = m_table; 232 m_table = allocArray!(TableEntry, ALLOC)(new_size); 233 234 /// fixme: Use initializeAll ? 235 /// 236 foreach (ref el; m_table) { 237 static if (is(Key == struct)) { 238 emplace(cast(UnConst!Key*)&el.key); 239 static if (Traits.clearValue !is Key.init) 240 el.key = cast(UnConst!Key)Traits.clearValue; 241 } else el.key = cast(UnConst!Key)Traits.clearValue; 242 emplace(&el.value); 243 } 244 foreach (ref el; oldtable) 245 if (!Traits.equals(el.key, Traits.clearValue)) { 246 auto idx = findInsertIndex(el.key); 247 (cast(ubyte[])(&m_table[idx])[0 .. 1])[] = (cast(ubyte[])(&el)[0 .. 1])[]; 248 } 249 250 if (oldtable) freeArray!(TableEntry, ALLOC)(oldtable, 0); 251 logTrace("Now have ", m_table.length); 252 } 253 254 void setupHasher() { 255 256 257 static if ((__traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toArray") ) || 258 __traits(hasMember, Key, "toArray")) 259 { 260 m_hasher = (Key k) { 261 import std.typecons : scoped; 262 import memutils.vector : Array; 263 Array!ubyte s = k.toArray(); 264 size_t hash = hashOf(s[], 0); 265 return hash; 266 }; 267 } 268 else static if ((__traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toVector") ) || 269 __traits(hasMember, Key, "toVector")) 270 { 271 m_hasher = (Key k) { 272 import std.typecons : scoped; 273 import memutils.vector : Vector; 274 Vector!char s = k.toVector(); 275 size_t hash = hashOf(s[], 0); 276 return hash; 277 }; 278 } 279 else static if (( __traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toString") ) || 280 __traits(hasMember, Key, "toString")) 281 { 282 m_hasher = (Key k) { 283 string s = k.toString(); 284 size_t hash = typeid(string).getHash(&s); 285 // logTrace("string ", s, " hash:" , hash); 286 return hash; 287 }; 288 } 289 290 else static if (__traits(compiles, (){ Key t; size_t hash = t.toHash(); }())) { 291 static if (isPointer!Key || is(Unqual!Key == class)) m_hasher = k => k ? k.toHash() : 0; 292 else m_hasher = k => k.toHash(); 293 } else static if (__traits(compiles, (){ Key t; size_t hash = t.toHashShared(); }())) { 294 static if (isPointer!Key || is(Unqual!Key == class)) m_hasher = k => k ? k.toHashShared() : 0; 295 else m_hasher = k => k.toHashShared(); 296 } 297 else static if (__traits(hasMember, Key, "isRefCounted")) { 298 m_hasher = (k) { return typeid(typeof(*(Key()))).getHash(&k); }; 299 } 300 else { 301 m_hasher = (k) { return typeid(Key).getHash(&k); }; 302 } 303 } 304 } 305 306 307 struct DefaultHashMapTraits(Key) { 308 enum clearValue = Key.init; 309 static bool equals(in Key a, in Key b) 310 { 311 static if (__traits(hasMember, Key, "isRefCounted") && 312 is (typeof(*(Key())) == class) && 313 __traits(compiles, "bool c = a.opEquals(*b);")) 314 { 315 if (!a && b) { 316 return b.opEquals(*a); 317 } 318 else if (a && !b) { 319 return a.opEquals(*b); 320 } 321 else if (a && b) 322 { 323 return a.opEquals(*b); 324 } 325 else { 326 return true; 327 } 328 } 329 else static if (__traits(hasMember, Key, "isRefCounted") && is (typeof(*(Key())) == class)) { 330 return *a is *b; 331 } 332 else static if (isPointer!Key) { 333 return a is b; 334 } 335 else { 336 return a == b; 337 } 338 } 339 }