1 module stdx.collection.hashtable; 2 3 import stdx.collection.common; 4 import std.experimental.allocator : IAllocator, theAllocator, make, dispose; 5 import std.experimental.allocator.building_blocks.affix_allocator; 6 import std.experimental.allocator.gc_allocator; 7 //import stdx.collection.pair : Pair; 8 import stdx.collection.slist : SList; 9 import stdx.collection.array : Array; 10 11 debug(CollectionHashtable) import std.stdio; 12 13 version(unittest) 14 { 15 import std.experimental.allocator.mallocator; 16 import std.experimental.allocator.building_blocks.stats_collector; 17 18 private alias Alloc = StatsCollector!( 19 AffixAllocator!(Mallocator, uint), 20 Options.bytesUsed 21 ); 22 Alloc _allocator; 23 } 24 25 struct Hashtable(K, V) 26 { 27 import std.traits : isImplicitlyConvertible, Unqual, isArray; 28 import std.range.primitives : isInputRange, isForwardRange, isInfinite, 29 ElementType, hasLength; 30 import std.conv : emplace; 31 import std.typecons : Tuple, Nullable; 32 import core.atomic : atomicOp; 33 34 private: 35 alias KVPair = Tuple!(K, "key", V, "value"); 36 Array!(SList!(KVPair)) _buckets; 37 Array!size_t _numElems; // This needs to be ref counted 38 static enum double loadFactor = 0.75; 39 40 alias MutableAlloc = AffixAllocator!(IAllocator, size_t); 41 Mutable!MutableAlloc _ouroborosAllocator; 42 43 /// Returns the actual allocator from ouroboros 44 @trusted ref auto allocator(this _)() 45 { 46 assert(!_ouroborosAllocator.isNull); 47 return _ouroborosAllocator.get(); 48 } 49 50 public: 51 /// Constructs the ouroboros allocator from allocator if the ouroboros 52 //allocator wasn't previously set 53 @trusted bool setAllocator(IAllocator allocator) 54 { 55 if (_ouroborosAllocator.isNull) 56 { 57 _ouroborosAllocator = Mutable!(MutableAlloc)(allocator, 58 MutableAlloc(allocator)); 59 _buckets = Array!(SList!(KVPair))(allocator); 60 _numElems = Array!size_t(allocator); 61 return true; 62 } 63 return false; 64 } 65 66 @trusted IAllocator getAllocator(this _)() 67 { 68 return _ouroborosAllocator.isNull ? null : allocator().parent; 69 } 70 71 this(this _)(IAllocator allocator) 72 { 73 debug(CollectionHashtable) 74 { 75 writefln("Array.ctor: begin"); 76 scope(exit) writefln("Array.ctor: end"); 77 } 78 setAllocator(allocator); 79 } 80 81 this(U, this Qualified)(U assocArr) 82 if (is(U == Value[Key], Value : V, Key : K)) 83 { 84 this(theAllocator, assocArr); 85 } 86 87 this(U, this Qualified)(IAllocator allocator, U assocArr) 88 if (is(U == Value[Key], Value : V, Key : K)) 89 { 90 debug(CollectionHashtable) 91 { 92 writefln("Hashtable.ctor: begin"); 93 scope(exit) writefln("Hashtable.ctor: end"); 94 } 95 static if (is(Qualified == immutable) || is(Qualified == const)) 96 { 97 // Build a mutable hashtable on the stack and pass ownership to this 98 auto tmp = Hashtable!(K, V)(allocator, assocArr); 99 _buckets = cast(typeof(_buckets))(tmp._buckets); 100 _numElems = cast(typeof(_numElems))(tmp._numElems); 101 auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator)); 102 _ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))(); 103 } 104 else 105 { 106 setAllocator(allocator); 107 insert(assocArr); 108 } 109 } 110 111 size_t insert(U)(U assocArr) 112 if (is(U == Value[Key], Value : V, Key : K)) 113 { 114 debug(CollectionHashtable) 115 { 116 writefln("Hashtable.insert: begin"); 117 scope(exit) writefln("Hashtable.insert: end"); 118 } 119 120 setAllocator(theAllocator); 121 if (_buckets.empty) 122 { 123 auto reqCap = requiredCapacity(assocArr.length); 124 _buckets.reserve(reqCap); 125 _buckets.forceLength(reqCap); 126 _numElems.reserve(1); 127 _numElems.forceLength(1); 128 } 129 foreach(k, v; assocArr) 130 { 131 size_t pos = k.hashOf & (_buckets.length - 1); 132 if (_buckets[pos].empty) 133 { 134 _buckets[pos] = SList!(KVPair)(_buckets.getAllocator(), KVPair(k, v)); 135 } 136 else 137 { 138 _buckets[pos].insert(0, KVPair(k, v)); 139 } 140 } 141 _numElems[0] += assocArr.length; 142 return assocArr.length; 143 } 144 145 private size_t requiredCapacity(size_t numElems) 146 { 147 static enum size_t maxPow2 = cast(size_t)(1) << (size_t.sizeof * 8 - 1); 148 while (numElems & (numElems - 1)) 149 { 150 numElems &= (numElems - 1); 151 } 152 return numElems < maxPow2 ? 2 * numElems : maxPow2; 153 } 154 155 /// Returns number of key-value pairs 156 size_t length() const 157 { 158 return _numElems.empty ? 0 : _numElems[0]; 159 } 160 161 /// Returns number of buckets 162 size_t size() const 163 { 164 return _buckets.length; 165 } 166 167 bool empty(this _)() 168 { 169 return _buckets.empty || _numElems.empty || _numElems[0] == 0; 170 } 171 172 ref auto front(this _)() 173 { 174 debug(CollectionHashtable) 175 { 176 writefln("Hashtable.front: begin"); 177 scope(exit) writefln("Hashtable.front: end"); 178 } 179 auto tmpBuckets = _buckets; 180 while ((!tmpBuckets.empty) && tmpBuckets.front.empty) 181 { 182 tmpBuckets.popFront; 183 } 184 assert(!tmpBuckets.empty, "Hashtable.front: Hashtable is empty"); 185 return tmpBuckets.front.front; 186 } 187 188 void popFront() 189 { 190 debug(CollectionHashtable) 191 { 192 writefln("Hashtable.popFront: begin"); 193 scope(exit) writefln("Hashtable.popFront: end"); 194 } 195 if (!_buckets.isUnique) 196 { 197 _buckets = _buckets.dup(); 198 _numElems = _numElems.dup(); 199 } 200 while ((!_buckets.empty) && _buckets.front.empty) 201 { 202 _buckets.popFront; 203 } 204 assert(!_buckets.empty, "Hashtable.front: Hashtable is empty"); 205 _buckets.front.popFront; 206 --_numElems[0]; 207 // Find the next non-empty bucketList 208 if (_buckets.front.empty) 209 { 210 while ((!_buckets.empty) && _buckets.front.empty) 211 { 212 _buckets.popFront; 213 } 214 } 215 } 216 217 private const 218 void getKeyValues(string kv, BuckQual, ListQual, ArrQual)(BuckQual b, ListQual l, ref ArrQual values) 219 { 220 if (b.empty) 221 { 222 return; 223 } 224 else if (l.empty) 225 { 226 auto bTail = b.tail; 227 if (!bTail.empty) 228 { 229 getKeyValues!kv(bTail, bTail.front, values); 230 } 231 } 232 else 233 { 234 static if (kv.length) 235 { 236 mixin("values ~= l.front." ~ kv ~ ";"); 237 } 238 else 239 { 240 mixin("values ~= l.front;"); 241 } 242 getKeyValues!kv(b, l.tail, values); 243 } 244 } 245 246 Array!K keys(this _)() 247 { 248 debug(CollectionHashtable) 249 { 250 writefln("Hashtable.keys: begin"); 251 scope(exit) writefln("Hashtable.keys: end"); 252 } 253 254 Array!K keys; 255 auto tmp = _buckets; 256 if (!_buckets.empty) 257 //if (!tmp.empty) 258 { 259 //getKeyValues!("key")(tmp, tmp.front, keys); 260 getKeyValues!("key")(_buckets, _buckets.front, keys); 261 } 262 return keys; 263 } 264 265 Array!V values(this _)() 266 { 267 debug(CollectionHashtable) 268 { 269 writefln("Hashtable.values: begin"); 270 scope(exit) writefln("Hashtable.values: end"); 271 } 272 273 Array!V values; 274 if (!_buckets.empty) 275 { 276 getKeyValues!("value")(_buckets, _buckets.front, values); 277 } 278 279 return values; 280 } 281 282 283 Array!(Tuple!(K, "key", V, "value")) keyValuePairs(this _)() 284 { 285 debug(CollectionHashtable) 286 { 287 writefln("Hashtable.keyValuePairs: begin"); 288 scope(exit) writefln("Hashtable.keyValuePairs: end"); 289 } 290 291 Array!KVPair pairs; 292 if (!_buckets.empty) 293 { 294 getKeyValues!("")(_buckets, _buckets.front, pairs); 295 } 296 297 return pairs; 298 } 299 300 private const 301 Nullable!V getValue(ListQual)(ListQual list, ref K key) 302 { 303 if (list.empty) 304 { 305 return Nullable!V.init; 306 } 307 else if (list.front.key == key) 308 { 309 return Nullable!V(list.front.value); 310 } 311 else 312 { 313 return getValue(list.tail, key); 314 } 315 } 316 317 V get(this _)(K key, V nullValue) 318 { 319 debug(CollectionHashtable) 320 { 321 writefln("Hashtable.get: begin"); 322 scope(exit) writefln("Hashtable.get: end"); 323 } 324 325 size_t pos = key.hashOf & (_buckets.length - 1); 326 auto result = getValue(_buckets[pos], key); 327 if (!result.isNull) 328 { 329 return result.get; 330 } 331 return nullValue; 332 } 333 334 Nullable!V opIndex(this _)(K key) 335 { 336 debug(CollectionHashtable) 337 { 338 writefln("Hashtable.opIndex: begin"); 339 scope(exit) writefln("Hashtable.opIndex: end"); 340 } 341 342 size_t pos = key.hashOf & (_buckets.length - 1); 343 return getValue(_buckets[pos], key); 344 } 345 346 Nullable!V opIndexUnary(string op)(K key) 347 { 348 debug(CollectionHashtable) 349 { 350 writefln("Hashtable.opIndexUnary!" ~ op ~ ": begin"); 351 scope(exit) writefln("Hashtable.opIndexUnary!" ~ op ~ ": end"); 352 } 353 354 size_t pos = key.hashOf & (_buckets.length - 1); 355 foreach(ref pair; _buckets[pos]) 356 { 357 if (pair.key == key) 358 { 359 return Nullable!V(mixin(op ~ "pair.value")); 360 } 361 } 362 return Nullable!V.init; 363 } 364 365 Nullable!V opIndexAssign(U)(U val, K key) 366 if (isImplicitlyConvertible!(U, V)) 367 { 368 debug(CollectionHashtable) 369 { 370 writefln("Hashtable.opIndexAssign: begin"); 371 scope(exit) writefln("Hashtable.opIndexAssign: end"); 372 } 373 374 size_t pos = key.hashOf & (_buckets.length - 1); 375 foreach(ref pair; _buckets[pos]) 376 { 377 if (pair.key == key) 378 { 379 return Nullable!V(pair.value); 380 } 381 } 382 return Nullable!V.init; 383 } 384 385 Nullable!V opIndexOpAssign(string op, U)(U val, K key) 386 if (isImplicitlyConvertible!(U, V)) 387 { 388 debug(CollectionHashtable) 389 { 390 writefln("Hashtable.opIndexOpAssign: begin"); 391 scope(exit) writefln("Hashtable.opIndexOpAssign: end"); 392 } 393 394 size_t pos = key.hashOf & (_buckets.length - 1); 395 foreach(ref pair; _buckets[pos]) 396 { 397 if (pair.key == key) 398 { 399 return Nullable!V(mixin("pair.value" ~ op ~ "= val")); 400 } 401 } 402 return Nullable!V.init; 403 } 404 405 406 auto ref opBinary(string op, U)(auto ref U rhs) 407 if (op == "~" && 408 (is (U == typeof(this)) 409 || is (U : T) 410 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 411 )); 412 413 auto ref opAssign()(auto ref typeof(this) rhs) 414 { 415 debug(CollectionHashtable) 416 { 417 writefln("Hashtable.opAssign: begin"); 418 scope(exit) writefln("Hashtable.opAssign: end"); 419 } 420 421 _buckets = rhs._buckets; 422 _numElems = rhs._numElems; 423 _ouroborosAllocator = rhs._ouroborosAllocator; 424 return this; 425 } 426 427 void remove(K key); 428 429 void clear() 430 { 431 debug(CollectionHashtable) 432 { 433 writefln("Hashtable.clear: begin"); 434 scope(exit) writefln("Hashtable.clear: end"); 435 } 436 _buckets = Array!(SList!(KVPair))(getAllocator()); 437 _numElems = Array!size_t(getAllocator()); 438 } 439 440 void rehash(); 441 } 442 443 @trusted unittest 444 { 445 import std.algorithm.comparison : equal; 446 447 auto h = Hashtable!(int, int)([1 : 10]); 448 449 assert(*h._buckets.prefCount(h._buckets._support) == 0); 450 assert(!h._buckets[0].empty && (*h._buckets[0].prefCount(h._buckets[0]._head) == 0)); 451 452 assert(equal(h.keys(), [1])); 453 assert(equal(h.values(), [10])); 454 assert(h.get(1, -1) == 10); 455 assert(h.get(200, -1) == -1); 456 assert(--h[1] == 9); 457 assert(h.get(1, -1) == 9); 458 459 assert(*h._buckets.prefCount(h._buckets._support) == 0); 460 assert(!h._buckets[0].empty && (*h._buckets[0].prefCount(h._buckets[0]._head) == 0)); 461 } 462 463 @trusted unittest 464 { 465 import std.typecons : Tuple; 466 467 auto h2 = Hashtable!(int, int)(); 468 assert(h2.length == 0); 469 h2.insert([1 : 10]); 470 assert(h2.length == 1); 471 assert(h2._buckets[0].front == Tuple!(int, int)(1, 10)); 472 h2.clear(); 473 assert(h2.length == 0); 474 assert(h2.empty); 475 } 476 477 @trusted unittest 478 { 479 import std.algorithm.comparison : equal; 480 import std.typecons : Tuple; 481 482 auto h3 = immutable Hashtable!(int, int)([1 : 10]); 483 assert(h3._buckets[0].front == Tuple!(int, int)(1, 10)); 484 assert(h3.get(1, -1) == 10); 485 assert(equal(h3.values(), Array!int([10]))); 486 assert(equal(h3.keyValuePairs(), Array!(Tuple!(int, int))(Tuple!(int, int)(1, 10)))); 487 }