1 /** 2 * Tree Map 3 * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module containers.treemap; 9 10 private import containers.internal.node : shouldAddGCRange; 11 private import std.experimental.allocator.mallocator : Mallocator; 12 13 /** 14 * A key→value mapping where the keys are guaranteed to be sorted. 15 * Params: 16 * K = the key type 17 * V = the value type 18 * Allocator = the allocator to use. Defaults to `Mallocator`. 19 * less = the key comparison function to use 20 * supportGC = true to support storing GC-allocated objects, false otherwise 21 * cacheLineSize = the size of the internal nodes in bytes 22 */ 23 struct TreeMap(K, V, Allocator = Mallocator, alias less = "a < b", 24 bool supportGC = shouldAddGCRange!K || shouldAddGCRange!V, size_t cacheLineSize = 64) 25 { 26 this(this) @disable; 27 28 private import std.experimental.allocator.common : stateSize; 29 30 auto allocator() 31 { 32 return tree.allocator; 33 } 34 35 static if (stateSize!Allocator != 0) 36 { 37 /// No default construction if an allocator must be provided. 38 this() @disable; 39 40 /** 41 * Use the given `allocator` for allocations. 42 */ 43 this(Allocator allocator) 44 { 45 tree = TreeType(allocator); 46 } 47 } 48 49 void clear() 50 { 51 tree.clear(); 52 } 53 54 /** 55 * Inserts or overwrites the given key-value pair. 56 */ 57 void insert(const K key, V value) @trusted 58 { 59 auto tme = TreeMapElement(cast(ContainerStorageType!K) key, value); 60 auto r = tree.equalRange(tme); 61 if (r.empty) 62 tree.insert(tme, true); 63 else 64 r._containersFront().value = value; 65 } 66 67 /// Supports $(B treeMap[key] = value;) syntax. 68 void opIndexAssign(V value, const K key) 69 { 70 insert(key, value); 71 } 72 73 /** 74 * Supports $(B treeMap[key]) syntax. 75 * 76 * Throws: RangeError if the key does not exist. 77 */ 78 auto opIndex(this This)(const K key) inout 79 { 80 alias CET = ContainerElementType!(This, V); 81 auto tme = TreeMapElement(cast(ContainerStorageType!K) key); 82 return cast(CET) tree.equalRange(tme).front.value; 83 } 84 85 /** 86 * Returns: the value associated with the given key, or the given `defaultValue`. 87 */ 88 auto get(this This)(const K key, lazy V defaultValue) inout @trusted 89 { 90 alias CET = ContainerElementType!(This, V); 91 auto tme = TreeMapElement(key); 92 auto er = tree.equalRange(tme); 93 if (er.empty) 94 return cast(CET) defaultValue; 95 else 96 return cast(CET) er.front.value; 97 } 98 99 /** 100 * If the given key does not exist in the TreeMap, adds it with 101 * the value `defaultValue`. 102 * 103 * Params: 104 * key = the key to look up 105 * defaultValue = the default value 106 * 107 * Returns: A pointer to the existing value, or a pointer to the inserted 108 * value. 109 */ 110 auto getOrAdd(this This)(const K key, lazy V defaultValue) 111 { 112 alias CET = ContainerElementType!(This, V); 113 auto tme = TreeMapElement(key); 114 auto er = tree.equalRange(tme); 115 if (er.empty) 116 { 117 // TODO: This does two lookups and should be made faster. 118 tree.insert(TreeMapElement(key, defaultValue)); 119 return cast(CET*) &tree.equalRange(tme)._containersFront().value; 120 } 121 else 122 { 123 return cast(CET*) &er._containersFront().value; 124 } 125 } 126 127 /** 128 * Removes the key→value mapping for the given key. 129 * 130 * Params: key = the key to remove 131 * Returns: true if the key existed in the map 132 */ 133 bool remove(const K key) 134 { 135 auto tme = TreeMapElement(cast(ContainerStorageType!K) key); 136 return tree.remove(tme); 137 } 138 139 /** 140 * Returns: true if the mapping contains the given key 141 */ 142 bool containsKey(const K key) inout pure nothrow @nogc @trusted 143 { 144 auto tme = TreeMapElement(cast(ContainerStorageType!K) key); 145 return tree.contains(tme); 146 } 147 148 /** 149 * Returns: true if the mapping is empty 150 */ 151 bool empty() const pure nothrow @property @safe @nogc 152 { 153 return tree.empty; 154 } 155 156 /** 157 * Returns: the number of key→value pairs in the map 158 */ 159 size_t length() inout pure nothrow @property @safe @nogc 160 { 161 return tree.length; 162 } 163 164 /** 165 * Returns: a GC-allocated array of the keys in the map 166 */ 167 auto keys(this This)() inout pure @property @trusted 168 { 169 import std.array : array; 170 171 return byKey!(This)().array(); 172 } 173 174 /** 175 * Returns: a range of the keys in the map 176 */ 177 auto byKey(this This)() inout pure @trusted @nogc 178 { 179 import std.algorithm.iteration : map; 180 alias CETK = ContainerElementType!(This, K); 181 182 return tree[].map!(a => cast(CETK) a.key); 183 } 184 185 /** 186 * Returns: a GC-allocated array of the values in the map 187 */ 188 auto values(this This)() inout pure @property @trusted 189 { 190 import std.array : array; 191 192 return byValue!(This)().array(); 193 } 194 195 /** 196 * Returns: a range of the values in the map 197 */ 198 auto byValue(this This)() inout pure @trusted @nogc 199 { 200 import std.algorithm.iteration : map; 201 alias CETV = ContainerElementType!(This, V); 202 203 return tree[].map!(a => cast(CETV) a.value); 204 } 205 206 /// ditto 207 alias opSlice = byValue; 208 209 /** 210 * Returns: a range of the kev/value pairs in this map. The element type of 211 * this range is a struct with `key` and `value` fields. 212 */ 213 auto byKeyValue(this This)() inout pure @trusted 214 { 215 import std.algorithm.iteration : map; 216 alias CETV = ContainerElementType!(This, V); 217 218 struct KeyValue 219 { 220 const K key; 221 CETV value; 222 } 223 224 return tree[].map!(n => KeyValue(n.key, cast(CETV) n.value)); 225 } 226 227 /** 228 * Returns: The value associated with the first key in the map. 229 */ 230 auto front(this This)() inout pure @trusted 231 { 232 alias CETV = ContainerElementType!(This, V); 233 234 return cast(CETV) tree.front.value; 235 } 236 237 /** 238 * Returns: The value associated with the last key in the map. 239 */ 240 auto back(this This)() inout pure @trusted 241 { 242 alias CETV = ContainerElementType!(This, V); 243 244 return cast(CETV) tree.back.value; 245 } 246 247 private: 248 249 import containers.ttree : TTree; 250 import containers.internal.storage_type : ContainerStorageType; 251 import containers.internal.element_type : ContainerElementType; 252 253 enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V); 254 255 static struct TreeMapElement 256 { 257 ContainerStorageType!K key; 258 ContainerStorageType!V value; 259 int opCmp(ref const TreeMapElement other) const 260 { 261 import std.functional : binaryFun; 262 return binaryFun!less(key, other.key); 263 } 264 } 265 266 alias TreeType = TTree!(TreeMapElement, Allocator, false, "a.opCmp(b) > 0", useGC, cacheLineSize); 267 TreeType tree; 268 } 269 270 version(emsi_containers_unittest) @system unittest 271 { 272 TreeMap!(string, string) tm; 273 tm["test1"] = "hello"; 274 tm["test2"] = "world"; 275 assert(tm.get("test1", "something") == "hello"); 276 tm.remove("test1"); 277 tm.remove("test2"); 278 assert(tm.length == 0); 279 assert(tm.empty); 280 assert(tm.get("test4", "something") == "something"); 281 assert(tm.get("test4", "something") == "something"); 282 } 283 284 version(emsi_containers_unittest) unittest 285 { 286 import std.range.primitives : walkLength; 287 import std.stdio : stdout; 288 import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; 289 import std.experimental.allocator.building_blocks.free_list : FreeList; 290 import std.experimental.allocator.building_blocks.region : Region; 291 import std.experimental.allocator.building_blocks.stats_collector : StatsCollector; 292 293 StatsCollector!(FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024)), 294 64)) allocator; 295 { 296 auto intMap = TreeMap!(int, int, typeof(&allocator))(&allocator); 297 foreach (i; 0 .. 10_000) 298 intMap[i] = 10_000 - i; 299 assert(intMap.length == 10_000); 300 } 301 assert(allocator.numAllocate == allocator.numDeallocate); 302 assert(allocator.bytesUsed == 0); 303 } 304 305 version(emsi_containers_unittest) unittest 306 { 307 import std.algorithm.comparison : equal; 308 import std.algorithm.iteration : each; 309 import std.range : repeat, take; 310 311 TreeMap!(int, int) tm; 312 int[] a = [1, 2, 3, 4, 5]; 313 a.each!(a => tm[a] = 0); 314 assert(equal(tm.keys, a)); 315 assert(equal(tm.values, repeat(0).take(a.length))); 316 } 317 318 version(emsi_containers_unittest) unittest 319 { 320 static class Foo 321 { 322 string name; 323 } 324 325 TreeMap!(string, Foo) tm; 326 auto f = new Foo; 327 tm["foo"] = f; 328 } 329 330 331 version(emsi_containers_unittest) unittest 332 { 333 import std.uuid : randomUUID; 334 import std.range.primitives : walkLength; 335 336 auto hm = TreeMap!(string, int)(); 337 assert (hm.length == 0); 338 assert (!hm.remove("abc")); 339 hm["answer"] = 42; 340 assert (hm.length == 1); 341 assert (hm.containsKey("answer")); 342 hm.remove("answer"); 343 assert (hm.length == 0); 344 hm["one"] = 1; 345 hm["one"] = 1; 346 assert (hm.length == 1); 347 assert (hm["one"] == 1); 348 hm["one"] = 2; 349 assert(hm["one"] == 2); 350 foreach (i; 0 .. 1000) 351 { 352 hm[randomUUID().toString] = i; 353 } 354 assert (hm.length == 1001); 355 assert (hm.keys().length == hm.length); 356 assert (hm.values().length == hm.length); 357 () @nogc { 358 assert (hm.byKey().walkLength == hm.length); 359 assert (hm.byValue().walkLength == hm.length); 360 assert (hm[].walkLength == hm.length); 361 assert (hm.byKeyValue().walkLength == hm.length); 362 }(); 363 foreach (v; hm) {} 364 365 auto hm2 = TreeMap!(char, char)(); 366 hm2['a'] = 'a'; 367 368 TreeMap!(int, int) hm3; 369 assert (hm3.get(100, 20) == 20); 370 hm3[100] = 1; 371 assert (hm3.get(100, 20) == 1); 372 auto pValue = hm3.containsKey(100); 373 assert(pValue == 1); 374 } 375 376 version(emsi_containers_unittest) unittest 377 { 378 static class Foo 379 { 380 string name; 381 } 382 383 void someFunc(const scope ref TreeMap!(string,Foo) map) @safe 384 { 385 foreach (kv; map.byKeyValue()) 386 { 387 assert (kv.key == "foo"); 388 assert (kv.value.name == "Foo"); 389 } 390 } 391 392 auto hm = TreeMap!(string, Foo)(); 393 auto f = new Foo; 394 f.name = "Foo"; 395 hm.insert("foo", f); 396 assert(hm.containsKey("foo")); 397 } 398 399 // Issue #54 400 version(emsi_containers_unittest) unittest 401 { 402 TreeMap!(string, int) map; 403 map.insert("foo", 0); 404 map.insert("bar", 0); 405 406 foreach (key; map.keys()) 407 map[key] = 1; 408 foreach (key; map.byKey()) 409 map[key] = 1; 410 411 foreach (value; map.byValue()) 412 assert(value == 1); 413 foreach (value; map.values()) 414 assert(value == 1); 415 } 416 417 version(emsi_containers_unittest) unittest 418 { 419 TreeMap!(int, int) map; 420 auto p = map.getOrAdd(1, 1); 421 assert(*p == 1); 422 } 423 424 version(emsi_containers_unittest) unittest 425 { 426 import std.uuid : randomUUID; 427 import std.range.primitives : walkLength; 428 //import std.stdio; 429 430 auto hm = TreeMap!(string, int)(); 431 foreach (i; 0 .. 1_000_000) 432 { 433 auto str = randomUUID().toString; 434 //writeln("Inserting ", str); 435 hm[str] = i; 436 //if (i > 0 && i % 100 == 0) 437 //writeln(i); 438 } 439 //writeln(hm.buckets.length); 440 441 import std.algorithm.sorting:sort; 442 //ulong[ulong] counts; 443 //foreach (i, ref bucket; hm.buckets[]) 444 //counts[bucket.length]++; 445 //foreach (k; counts.keys.sort()) 446 //writeln(k, "=>", counts[k]); 447 }