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