1 // Written in the D programming language. 2 3 module swisstable.map; 4 5 import swisstable.group; 6 import swisstable.hash; 7 import swisstable.table; 8 9 import std.experimental.allocator.mallocator : Mallocator; 10 11 /** 12 Swiss tables hash map 13 14 Examples: 15 --- 16 // Initialize Map with capacity 17 auto map = Map!(string, string)(10); // Default capacity is 0 18 map["key"] = "value"; 19 auto p = "key" in map; 20 if (p) 21 *p = "new value"; 22 writeln(map["key"]); // Print "new value" 23 24 // Support AA properties 25 map.length; 26 map.clear; 27 map.rehash; 28 map.dup; 29 K[] keys = map.keys; 30 V[] values = map.values; 31 foreach (k; map.byKey()) {} 32 foreach (v; map.byValue()) {} 33 foreach (e; map.byKeyValue()) {} // e has key and value properties 34 --- 35 36 Params: 37 K = key type 38 V = value type 39 Allocator = Allocator type to use internal allocation. Default is "shared const Mallocator" 40 initSlotInIndex = Assign V.init instead of RangeError when key not found. Default is "true" 41 42 `initSlotInIndex` is used for `++map[key]` use-cases. 43 44 NOTE: Map doesn't guarantee pointer stability. Range and returned pointers are invalidated after insert/rehash. 45 46 Copyright: Copyright 2024- Masahiro Nakagawa 47 Authros: Masahiro Nakagawa 48 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache License Version 2.0) 49 */ 50 struct Map(K, V, Allocator = shared const Mallocator, bool initSlotInIndex = true) 51 { 52 import std.traits : hasIndirections; 53 import std.typecons : Tuple; 54 55 // These string enums are used in Table template 56 enum CompCondition = "key == _slots[offset].key"; 57 enum HashKeyArg = "slot.key"; 58 59 alias SlotType = Tuple!(K, "key", V, "value"); 60 mixin Table!(Map, K, SlotType, Allocator, hasIndirections!K || hasIndirections!V); 61 62 public: 63 static if (initSlotInIndex) { 64 /** 65 * $(B map[key]) syntax support. 66 * 67 * With initSlotInIndex=true, $(B key)'s slot will be initialized if key doesn't exist. 68 */ 69 ref V opIndex(in K key) 70 { 71 import std.traits : Unqual; 72 73 bool found; 74 size_t pos = findOrPrepareInsert(key, found); 75 if (!found) 76 _slots[pos] = SlotType(cast(Unqual!K)(key), V.init); 77 78 return _slots[pos].value; 79 } 80 } else { 81 ref inout(V) opIndex(in K key) inout 82 { 83 import core.exception : onRangeError; 84 85 bool found; 86 size_t pos = find(key, found); 87 if (found) 88 return _slots[pos].value; 89 90 onRangeError(); 91 } 92 } 93 94 /** 95 * $(B key in map) syntax support. 96 * 97 * Returns: pointer to value corresponding to key 98 */ 99 inout(V)* opBinaryRight(string op)(in K key) inout if (op == "in") 100 { 101 bool found; 102 size_t pos = find(key, found); 103 if (found) 104 return &_slots[pos].value; 105 106 return null; 107 } 108 109 /** 110 * $(B map[key] = value) syntax support. 111 */ 112 void opIndexAssign(in V value, in K key) 113 { 114 import std.traits : Unqual; 115 116 bool found; 117 size_t pos = findOrPrepareInsert(key, found); 118 if (found) 119 _slots[pos].value = cast(Unqual!V)value; 120 else 121 _slots[pos] = SlotType(cast(Unqual!K)key, cast(Unqual!V)value); 122 } 123 124 /** 125 * Remove key from the map. 126 * 127 * Params: 128 * key = key to search for 129 * 130 * Returns: true if given key does exist. otherwise false. 131 */ 132 bool remove(in K key) 133 { 134 if (_size == 0) 135 return false; 136 137 bool found; 138 size_t pos = find(key, found); 139 if (found) { 140 // destroy(_slots[pos]) is better? 141 destroy!(false)(_slots[pos]); 142 143 // erase_meta_only in original implementation 144 --_size; 145 if (wasNeverFull(_control, _capacity, pos)) { 146 setCtrl(_control, _capacity, pos, Control.Empty); 147 growthInfo.overwriteFullAsEmpty(); 148 return true; 149 } 150 151 growthInfo.overwriteFullAsTombstone(); 152 setCtrl(_control, _capacity, pos, Control.Tombstone); 153 return true; 154 } 155 156 return false; 157 } 158 159 /// Returns: a GC allocated dynamic array, the elements of which are the keys in the map. 160 @property K[] keys() const nothrow 161 { 162 return funcKeysValuesBody!(K, "slot.key")(); 163 } 164 165 /// Returns: a GC allocated dynamic array, the elements of which are the values in the map. 166 @property V[] values() const nothrow 167 { 168 return funcKeysValuesBody!(V, "slot.value")(); 169 } 170 171 /// Returns: a forward range suitable for use as a $(B ForeachAggregate) which will iterate over the keys of the map. 172 Range!(IterMode.Key) byKey() nothrow 173 { 174 return typeof(return)(_control, _slots.ptr); 175 } 176 177 /// Returns: a forward range suitable for use as a $(B ForeachAggregate) which will iterate over the values of the map. 178 Range!(IterMode.Value) byValue() nothrow 179 { 180 return typeof(return)(_control, _slots.ptr); 181 } 182 183 /// Returns: a forward range suitable for use as a $(B ForeachAggregate) which will iterate over the key-value pairs of the map. 184 Range!(IterMode.Both) byKeyValue() nothrow 185 { 186 return typeof(return)(_control, _slots.ptr); 187 } 188 189 /// $(B foreach) support 190 int opApply(scope int delegate(const ref K, ref V) dg) 191 { 192 int result; 193 auto ctrl = _control; 194 auto slot = _slots.ptr; 195 196 skipEmptyOrTombstoneSlots(ctrl, slot); 197 while (*ctrl != Control.Sentinel) { 198 result = dg(slot.key, slot.value); 199 if (result) 200 break; 201 202 ++ctrl; 203 ++slot; 204 skipEmptyOrTombstoneSlots(ctrl, slot); 205 } 206 207 return result; 208 } 209 210 private @trusted: 211 enum IterMode { Key, Value, Both } 212 213 /// ForwardRange for byXXX functions 214 static struct Range(IterMode Mode) 215 { 216 private: 217 Control* _control = emptyGroup; 218 SlotType* _slots; 219 220 public nothrow @trusted: 221 @disable this(); 222 223 this(Control* ctrl, SlotType* slots) 224 { 225 _control = ctrl; 226 _slots = slots; 227 skipEmptyOrTombstoneSlots(_control, _slots); 228 } 229 230 @property bool empty() const 231 { 232 return *_control == Control.Sentinel; 233 } 234 235 static if (Mode == IterMode.Both) { 236 @property ref SlotType front() 237 { 238 return *_slots; 239 } 240 } else static if (Mode == IterMode.Key) { 241 @property ref K front() 242 { 243 return _slots.key; 244 } 245 } else { 246 @property ref V front() 247 { 248 return _slots.value; 249 } 250 } 251 252 void popFront() 253 { 254 ++_control; 255 ++_slots; 256 skipEmptyOrTombstoneSlots(_control, _slots); 257 } 258 259 typeof(this) save() 260 { 261 return typeof(this)(_control, _slots); 262 } 263 } 264 } 265 266 unittest 267 { 268 Map!(string, int) map; 269 270 map["k0"] = 9; 271 map["k1"]++; 272 273 assert(map.length == 2); 274 assert("k1" in map); 275 assert(map["k0"] == 9); 276 assert(map["k1"] == 1); 277 } 278 279 unittest 280 { 281 // initSlotInIndex is false 282 import core.exception : RangeError; 283 284 Map!(int, int, shared const Mallocator, false) map; 285 286 map[0] = 9; 287 try { 288 map[1]++; 289 assert(false, "don't reach here"); 290 } catch (RangeError) { 291 assert(map.length == 1); 292 assert(0 in map); 293 } 294 } 295 296 unittest 297 { 298 // remove and rehash 299 import std.algorithm : sort; 300 import std.conv : text; 301 302 enum N = 100; 303 string[] keys = new string[](N); 304 Map!(string, string) map; 305 306 foreach (i; 0..N) { 307 string k = text("k", i); 308 keys[i] = k; 309 map[k] = text("v", i);; 310 } 311 assert(map.length == 100); 312 313 foreach (i; 0..N / 2) 314 map.remove(keys[i]); 315 assert(map.length == N / 2); 316 assert(map.capacity == normalizeCapacity(N)); 317 318 map.rehash; 319 320 assert(map.capacity == normalizeCapacity(N / 2)); 321 foreach (i; N / 2..N) 322 assert(keys[i] in map); 323 324 auto ks = map.keys.sort; 325 auto vs = map.values.sort; 326 assert(ks[0] == "k50"); 327 assert(vs[0] == "v50"); 328 assert(ks[$ - 1] == "k99"); 329 assert(vs[$ - 1] == "v99"); 330 } 331 332 unittest 333 { 334 // dup 335 import std.algorithm : sort, equal; 336 import std.conv : text; 337 338 Map!(int, string) map; 339 340 enum N = 100; 341 foreach (i; 0..N) 342 map[i] = text("v", i); 343 344 auto nmap = map.dup; 345 assert(nmap.length == map.length); 346 assert(nmap._capacity == map.capacity); 347 assert(equal(nmap.keys.sort, map.keys.sort)); 348 assert(equal(nmap.values.sort, map.values.sort)); 349 350 nmap[10] = "hey"; 351 assert(nmap[10] == "hey"); 352 assert(map[10] == "v10"); 353 } 354 355 unittest 356 { 357 // clear 358 import std.algorithm : sort, equal; 359 import std.range : empty; 360 import std.conv : text; 361 362 Map!(int, string) map; 363 364 enum N = 100; 365 foreach (i; 0..N) 366 map[i] = text("v", i); 367 368 auto oldCap = map.capacity; 369 map.clear; 370 371 assert(map.length == 0); 372 assert(map.capacity == oldCap); 373 assert(map.keys.empty); 374 assert(map.values.empty); 375 376 foreach (i; 0..10) 377 map[i] = text("v", i); 378 map.clear(false); 379 380 assert(map.length == 0); 381 assert(map.capacity == 0); 382 assert(map.keys.empty); 383 assert(map.values.empty); 384 } 385 386 unittest 387 { 388 // struct key-value test 389 import std.conv : text; 390 391 static struct Test 392 { 393 int a; 394 double b; 395 string c; 396 397 this(ref return scope Test rhs) nothrow @safe 398 { 399 a = rhs.a; 400 b = rhs.b; 401 c = rhs.c; 402 } 403 } 404 405 enum N = 100; 406 Test[] tests; 407 Map!(Test, Test) map; 408 409 foreach (i; 0..N) { 410 auto t = Test(i, cast(double)i + 0.1, text("v", i)); 411 tests ~= t; 412 map[t] = t; 413 assert(map[t] == t); 414 } 415 416 foreach (i; 0..N) { 417 if (i % 2 == 0) { 418 map.remove(tests[i]); 419 assert(!(tests[i] in map)); 420 } 421 } 422 assert(map.length == N / 2); 423 424 foreach (i; 0..N) { 425 if (i % 2 == 1) { 426 auto t = tests[i]; 427 auto p = t in map; 428 assert(p); 429 p.a = 100000; 430 p.c = "test"; 431 assert(map[t].a == 100000); 432 assert(map[t].c == "test"); 433 } 434 } 435 436 map.clear; 437 438 // Test temporal objects 439 foreach (i; 0..N) { 440 map[Test(i, 0, text(i))] = Test(i, 0, text(i)); 441 assert(Test(i, 0, text(i)) in map); 442 assert(map.remove(Test(i, 0, text(i)))); 443 } 444 assert(map.length == 0); 445 } 446 447 unittest 448 { 449 // byXXX 450 import std.algorithm : canFind; 451 import std.conv : text; 452 import std.range : walkLength; 453 454 enum N = 100; 455 int[] keys = new int[](N); 456 string[] vals = new string[](N); 457 Map!(int, string) map; 458 459 foreach (ref k; map.byKey()) assert(false); 460 foreach (ref v; map.byValue()) assert(false); 461 foreach (ref e; map.byKeyValue()) assert(false); 462 463 foreach (i; 0..N) { 464 string v = text("v", i); 465 keys[i] = i; 466 vals[i] = v; 467 map[i] = v; 468 } 469 470 auto kr = map.byKey(); 471 foreach (ref k; kr.save) { 472 assert(keys.canFind(k)); 473 assert(k in map); 474 } 475 assert(walkLength(kr) == N); 476 477 auto vr = map.byValue(); 478 foreach (ref v; vr.save) 479 assert(vals.canFind(v)); 480 assert(walkLength(vr) == N); 481 482 auto kvr = map.byKeyValue(); 483 foreach (ref e; kvr.save) { 484 assert(keys.canFind(e.key)); 485 assert(vals.canFind(e.value)); 486 assert(e.key in map); 487 assert(map[e.key] == e.value); 488 } 489 assert(walkLength(kvr) == N); 490 491 // opApply 492 int count = 0; 493 foreach (ref k, ref v; map) { 494 count++; 495 assert(keys.canFind(k)); 496 assert(vals.canFind(v)); 497 } 498 assert(count == N); 499 }