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