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 static if (stateSize!Allocator != 0) 31 { 32 /// No default construction if an allocator must be provided. 33 this() @disable; 34 35 /** 36 * Use the given `allocator` for allocations. 37 */ 38 this(Allocator allocator) 39 { 40 tree = TreeType(allocator); 41 } 42 } 43 44 /** 45 * Inserts the given key-value pair. 46 */ 47 void insert(V value, K key) 48 { 49 auto tme = TreeMapElement(key, value); 50 tree.insert(tme); 51 } 52 53 /// Supports $(B treeMap[key] = value;) syntax. 54 alias opIndexAssign = insert; 55 56 /// Supports $(B treeMap[key]) syntax. 57 auto opIndex(this This)(K key) const 58 { 59 alias CET = ContainerElementType!(This, V); 60 auto tme = TreeMapElement(key); 61 return cast(CET) tree.equalRange(tme).front.value; 62 } 63 64 /** 65 * Removes the key→value mapping for the given key. 66 * Params: key = the key to remove 67 * Returns: true if the key existed in the map 68 */ 69 bool remove(K key) 70 { 71 auto tme = TreeMapElement(key); 72 return tree.remove(tme); 73 } 74 75 /// Returns: true if the mapping contains the given key 76 bool containsKey(K key) 77 { 78 auto tme = TreeMapElement(key); 79 return tree.contains(tme); 80 } 81 82 /// Returns: true if the mapping is empty 83 bool empty() const pure nothrow @property @safe @nogc 84 { 85 return tree.empty; 86 } 87 88 /// Returns: the number of key→value pairs in the map 89 size_t length() const pure nothrow @property @safe @nogc 90 { 91 return tree.length; 92 } 93 94 auto keys() const pure nothrow @property @safe @nogc 95 { 96 import std.algorithm.iteration : map; 97 return tree[].map!(a => a.key); 98 } 99 100 auto values() const pure nothrow @property @safe @nogc 101 { 102 import std.algorithm.iteration : map; 103 return tree[].map!(a => a.value); 104 } 105 106 /// Supports $(B foreach(k, v; treeMap)) syntax. 107 int opApply(this This)(int delegate(ref K, ref V) loopBody) 108 { 109 int result; 110 foreach (ref tme; tree[]) 111 { 112 result = loopBody(tme.key, tme.value); 113 if (result) 114 break; 115 } 116 return result; 117 } 118 119 private: 120 121 import containers.ttree : TTree; 122 import containers.internal.storage_type : ContainerStorageType; 123 import containers.internal.element_type : ContainerElementType; 124 125 enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V); 126 127 static struct TreeMapElement 128 { 129 ContainerStorageType!K key; 130 ContainerStorageType!V value; 131 int opCmp(ref const TreeMapElement other) const 132 { 133 import std.functional : binaryFun; 134 return binaryFun!less(key, other.key); 135 } 136 } 137 138 alias TreeType = TTree!(TreeMapElement, Allocator, false, "a.opCmp(b) > 0", useGC, cacheLineSize); 139 static if (stateSize!Allocator == 0) 140 TreeType tree = void; 141 else 142 TreeType tree; 143 } 144 145 unittest 146 { 147 TreeMap!(string, string) tm; 148 tm["test1"] = "hello"; 149 tm["test2"] = "world"; 150 tm.remove("test1"); 151 tm.remove("test2"); 152 } 153 154 unittest 155 { 156 import std.experimental.allocator.building_blocks.free_list : FreeList; 157 import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; 158 import std.experimental.allocator.building_blocks.region : Region; 159 import std.experimental.allocator.building_blocks.stats_collector : StatsCollector; 160 import std.stdio : stdout; 161 import std.algorithm.iteration : walkLength; 162 163 StatsCollector!(FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024)), 164 64)) allocator; 165 { 166 auto intMap = TreeMap!(int, int, typeof(&allocator))(&allocator); 167 foreach (i; 0 .. 10_000) 168 intMap[i] = 10_000 - i; 169 assert(intMap.length == 10_000); 170 } 171 assert(allocator.numAllocate == allocator.numDeallocate); 172 assert(allocator.bytesUsed == 0); 173 } 174 175 unittest 176 { 177 import std.algorithm.iteration : each; 178 import std.algorithm.comparison : equal; 179 import std.range : repeat, take; 180 181 TreeMap!(int, int) tm; 182 int[] a = [1, 2, 3, 4, 5]; 183 a.each!(a => tm[a] = 0); 184 assert(equal(tm.keys, a)); 185 assert(equal(tm.values, repeat(0).take(a.length))); 186 }