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 }