The OpenD Programming Language

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 }