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