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 }