1 /** 2 Associative Arrays 3 4 This module implements BTree-backed associative arrays. 5 @nogc associative array, replacement for std::map. 6 7 Copyright: 8 Copyright © 2023-2025, Kitsunebi Games 9 Copyright © 2023-2025, Inochi2D Project 10 11 Copyright: Guillaume Piolat 2015-2024. 12 Copyright: Copyright (C) 2008- by Steven Schveighoffer. Other code 13 Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. 14 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 15 Authors: 16 Steven Schveighoffer, 17 $(HTTP erdani.com, Andrei Alexandrescu), 18 Guillaume Piolat, 19 Luna Nielsen 20 */ 21 module nulib.collections.map; 22 import nulib.collections.internal.btree; 23 import numem; 24 25 @nogc: 26 27 /** 28 An associative container which contains key-value pairs. 29 30 Notes: 31 Weak types do not own the memory of their contents. 32 It is up to you to free any indicies. 33 */ 34 alias weak_map(Key, Value) = MapImpl!(Key, Value, (a, b) => a < b, false, false); 35 36 /** 37 An associative container which contains key-value pairs. 38 */ 39 alias map(Key, Value) = MapImpl!(Key, Value, (a, b) => a < b, false, true); 40 41 /** 42 An associative container which contains key-value pairs. 43 44 A multimap may contain multiple entries with the same $(D Key) value. 45 46 Notes: 47 Weak types do not own the memory of their contents. 48 It is up to you to free any indicies. 49 */ 50 alias weak_multimap(Key, Value) = MapImpl!(Key, Value, (a, b) => a < b, true, false); 51 52 /** 53 An associative container which contains key-value pairs. 54 55 A multimap may contain multiple entries with the same $(D Key) value. 56 */ 57 alias multimap(Key, Value) = MapImpl!(Key, Value, (a, b) => a < b, true, true); 58 59 /** 60 Tree-map, designed to replace std::map usage. 61 The API should looks closely like the builtin associative arrays. 62 O(log(n)) insertion, removal, and search time. 63 */ 64 struct MapImpl(K, V, alias less = KeyCompareDefault!Key, bool allowDuplicates = false, bool ownsMemory = false) { 65 public: 66 @nogc: 67 68 @disable this(this); 69 70 @trusted 71 ~this() { 72 } 73 74 /// Insert an element in the container, if the container doesn't already contain 75 /// an element with equivalent key. 76 /// Returns: `true` if the insertion took place. 77 @trusted 78 bool insert(K key, V value) { 79 return _tree.insert(key, value); 80 } 81 82 /// Removes an element from the container. 83 /// Returns: `true` if the removal took place. 84 @trusted 85 bool remove(K key) { 86 87 // Delete memory if this map owns it. 88 static if (ownsMemory) { 89 if (key in _tree) { 90 nogc_delete(_tree[key]); 91 } 92 } 93 94 return _tree.remove(key) != 0; 95 } 96 97 /// Removes all elements from the map. 98 @trusted 99 void clearContents() { 100 nogc_delete(_tree); 101 // _tree reset to .init, still valid 102 } 103 104 /// Returns: A pointer to the value corresponding to this key, or null if not available. 105 /// Live builtin associative arrays. 106 @trusted 107 inout(V)* opBinaryRight(string op)(K key) inout if (op == "in") { 108 return key in _tree; 109 } 110 111 /// Returns: A reference to the value corresponding to this key. 112 /// Null pointer crash if key doesn't exist. 113 @trusted 114 ref inout(V) opIndex(K key) inout { 115 inout(V)* p = key in _tree; 116 assert(p !is null); 117 return *p; 118 } 119 120 /// Updates a value associated with a key, creates it if necessary. 121 @trusted 122 void opIndexAssign(V value, K key) { 123 V* p = key in _tree; 124 if (p is null) { 125 insert(key, value); // PERF: this particular call can assume no-dupe 126 } else 127 *p = value; 128 } 129 130 /// Returns: `true` if this key is contained. 131 @trusted 132 bool contains(K key) const { 133 return _tree.contains(key); 134 } 135 136 /// Returns: Number of elements in the map. 137 @trusted 138 size_t length() const { 139 return _tree.length; 140 } 141 142 /// Returns: `ttue` is the map has no element. 143 @trusted 144 bool empty() const { 145 return _tree.empty; 146 } 147 148 // Iterate by value only 149 150 /// Fetch a forward range on all values. 151 @trusted 152 auto byValue() { 153 return _tree.byValue(); 154 } 155 156 /// ditto 157 @trusted 158 auto byValue() const { 159 return _tree.byValue(); 160 } 161 162 // default opSlice is like byValue for builtin associative arrays 163 alias opSlice = byValue; 164 165 // Iterate by key only 166 167 /// Fetch a forward range on all keys. 168 @trusted 169 auto byKey() { 170 return _tree.byKey(); 171 } 172 173 /// ditto 174 @trusted 175 auto byKey() const { 176 return _tree.byKey(); 177 } 178 179 // Iterate by key-value 180 @trusted 181 auto byKeyValue() { 182 return _tree.byKeyValue(); 183 } 184 185 /// ditto 186 @trusted 187 auto byKeyValue() const { 188 return _tree.byKeyValue(); 189 } 190 191 /+ 192 // Iterate by single value (return a range where all elements have equal key) 193 194 /// Fetch a forward range on all elements with given key. 195 Range!(MapRangeType.value) byGivenKey(K key) 196 { 197 if (!isInitialized) 198 return Range!(MapRangeType.value).init; 199 200 auto kv = KeyValue(key, V.init); 201 return Range!(MapRangeType.value)(_rbt.range(kv)); 202 } 203 204 /// ditto 205 ConstRange!(MapRangeType.value) byGivenKey(K key) const 206 { 207 if (!isInitialized) 208 return ConstRange!(MapRangeType.value).init; 209 210 auto kv = KeyValue(key, V.init); 211 return ConstRange!(MapRangeType.value)(_rbt.range(kv)); 212 } 213 214 /// ditto 215 ImmutableRange!(MapRangeType.value) byGivenKey(K key) immutable 216 { 217 if (!isInitialized) 218 return ImmutableRange!(MapRangeType.value).init; 219 220 auto kv = KeyValue(key, V.init); 221 return ImmutableRange!(MapRangeType.value)(_rbt.range(kv)); 222 }* 223 +/ 224 225 private: 226 227 alias InternalTree = BTree!(K, V, less, allowDuplicates, false); 228 InternalTree _tree; 229 } 230 231 @("map: initialization") 232 unittest { 233 // It should be possible to use most function of an uninitialized Map 234 // All except functions returning a range will work. 235 map!(int, string) m; 236 237 assert(m.length == 0); 238 assert(m.empty); 239 assert(!m.contains(7)); 240 241 auto range = m.byKey(); 242 assert(range.empty); 243 foreach (e; range) { 244 } 245 246 m[1] = "fun"; 247 } 248 249 @("map: key collission") 250 unittest { 251 void test(bool removeKeys) @nogc { 252 { 253 auto test = nogc_new!(map!(int, string))(); 254 int N = 100; 255 foreach (i; 0 .. N) { 256 int key = (i * 69069) % 65536; 257 test.insert(key, "this is a test"); 258 } 259 foreach (i; 0 .. N) { 260 int key = (i * 69069) % 65536; 261 assert(test.contains(key)); 262 } 263 264 if (removeKeys) { 265 foreach (i; 0 .. N) { 266 int key = (i * 69069) % 65536; 267 test.remove(key); 268 } 269 } 270 foreach (i; 0 .. N) { 271 int key = (i * 69069) % 65536; 272 assert(removeKeys ^ test.contains(key)); // either keys are here or removed 273 } 274 } 275 } 276 277 test(true); 278 test(false); 279 } 280 281 @("map: lookup") 282 unittest { 283 // Associative array of ints that are 284 // indexed by string keys. 285 // The KeyType is string. 286 map!(string, int) aa; 287 aa["hello"] = 3; // set value associated with key "hello" to 3 288 int value = aa["hello"]; // lookup value from a key 289 assert(value == 3); 290 291 int* p; 292 293 p = ("hello" in aa); 294 if (p !is null) { 295 *p = 4; // update value associated with key 296 assert(aa["hello"] == 4); 297 } 298 299 aa.remove("hello"); 300 }