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