1 /**
2     Internal hash map implementation.
3 
4     Copyright: © 2013 RejectedSoftware e.K.
5     License: Subject to the terms of the MIT license, as written in the included LICENSE file.
6     Authors: Sönke Ludwig
7 */
8 module memutils.hashmap;
9 
10 import std.conv : emplace, to;
11 import std.traits;
12 import std.algorithm : countUntil;
13 import memutils.refcounted;
14 import memutils.constants;
15 import memutils.helpers;
16 import memutils.allocators;
17 import memutils.utils;
18 
19 
20 alias HashMapRef(Key, Value, ALLOC = ThreadMem) = RefCounted!(HashMap!(Key, Value, ALLOC), ALLOC);
21 
22 struct HashMap(Key, Value, ALLOC = ThreadMem)
23 {
24 	@disable this(this);
25 
26 	alias Traits = DefaultHashMapTraits!Key;
27 	struct TableEntry {
28 		UnConst!Key key;
29 		Value value;
30 		
31 		this(Key key, Value value) { this.key = cast(UnConst!Key)key; this.value = value; }
32 	}
33 	private {
34 		TableEntry[] m_table; // NOTE: capacity is always POT
35 		size_t m_length;
36 		hash_t delegate(Key) m_hasher;
37 		bool m_resizing;
38 	}
39 	
40 	~this()
41 	{
42 		clear();
43 		if (m_table) freeArray!(TableEntry, ALLOC)(m_table);
44 	}
45 
46 	@property bool empty() const { return length == 0; }
47 	@property size_t length() const { return m_length; }
48 	
49 	void remove(Key key)
50 	{
51 		//static if (Key.stringof.countUntil("OIDImpl") != -1) logError("Removing: ", key.toString());
52 		auto idx = findIndex(key);
53 		assert (idx != size_t.max, "Removing non-existent element.");
54 		auto i = idx;
55 		while (true) {
56 			m_table[i].key = Traits.clearValue;
57 			m_table[i].value = Value.init;
58 			size_t j = i, r;
59 			do {
60 				if (++i >= m_table.length) i -= m_table.length;
61 				if (Traits.equals(m_table[i].key, Traits.clearValue)) {
62 					m_length--;
63 					return;
64 				}
65 				r = m_hasher(m_table[i].key) & (m_table.length-1);
66 
67 			} while ((j<r && r<=i) || (i<j && j<r) || (r<=i && i<j));
68 			m_table[j] = m_table[i];
69 		}
70 	}
71 	
72 	Value get(in Key key, lazy Value default_value = Value.init) const
73 	{
74 		auto idx = this.findIndex(key);
75 		if (idx == size_t.max) return default_value;
76 		const Value ret = m_table[idx].value;
77 		return *cast(Value*)&ret;
78 	}
79 	/*	
80 	static if (!is(typeof({ Value v; const(Value) vc; v = vc; }))) {
81 		const(Value) get(Key key, lazy const(Value) default_value = Value.init)
82 		{
83 			auto idx = findIndex(key);
84 			if (idx == size_t.max) return default_value;
85 			return m_table[idx].value;
86 		}
87 	}*/
88 	
89 	void clear()
90 	{
91 		foreach (i; 0 .. m_table.length)
92 		if (!Traits.equals(m_table[i].key, Traits.clearValue)) {
93 			m_table[i].key = Traits.clearValue;
94 			m_table[i].value = Value.init;
95 		}
96 		m_length = 0;
97 	}
98 	
99 	void set(Key key, Value value) {
100 		opIndexAssign(value, key);
101 	}
102 
103 	
104 	void opIndexAssign(inout Value value, in Key key)
105 	{
106 		//assert(!Traits.equals(key, Traits.clearValue), "Inserting clear value into hash map.");
107 		grow(1);
108 		auto i = findInsertIndex(key);
109 		if (!Traits.equals(m_table[i].key, key)) m_length++;
110 		m_table[i] = TableEntry(*cast(Key*) &key, *cast(Value*) &value);
111 	}
112 	
113 	ref inout(Value) opIndex(Key key) inout {
114 		auto idx = findIndex(key);
115 		assert (idx != size_t.max, "Accessing non-existent key type: " ~ Key.stringof ~ " value: " ~ key.to!string);
116 		return m_table[idx].value;
117 	}
118 	
119 	Value opIndex(Key key) const {
120 		auto idx = findIndex(key);
121 		assert (idx != size_t.max, "Accessing non-existent key type: " ~ Key.stringof ~ " value: " ~ key.to!string);
122 		const Value ret = m_table[idx].value;
123 		return *cast(Value*) &ret;
124 	}
125 	
126 	inout(Value)* opBinaryRight(string op)(Key key)
127 	inout if (op == "in") {
128 		auto idx = findIndex(key);
129 		if (idx == size_t.max) return null;
130 		return &m_table[idx].value;
131 	}
132 	
133 	int opApply(int delegate(ref Value) del)
134 	{
135 		foreach (i; 0 .. m_table.length)
136 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
137 				if (auto ret = del(m_table[i].value))
138 					return ret;
139 		return 0;
140 	}
141 	
142 	int opApply(int delegate(in ref Value) del)
143 	const {
144 		foreach (i; 0 .. m_table.length)
145 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
146 				if (auto ret = del(m_table[i].value))
147 					return ret;
148 		return 0;
149 	}
150 	
151 	int opApply(int delegate(const ref Key, ref Value) del)
152 	{
153 		foreach (i; 0 .. m_table.length)
154 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
155 				if (auto ret = del(m_table[i].key, m_table[i].value))
156 					return ret;
157 		return 0;
158 	}
159 	
160 	int opApply(int delegate(in ref Key, in ref Value) del)
161 	const {
162 		foreach (i; 0 .. m_table.length)
163 			if (!Traits.equals(m_table[i].key, Traits.clearValue))
164 				if (auto ret = del(m_table[i].key, m_table[i].value))
165 					return ret;
166 		return 0;
167 	}
168 	
169 	private size_t findIndex(in Key key)
170 	const {
171 		if (m_length == 0) return size_t.max;
172 		size_t start = m_hasher(*cast(Key*) &key) & (m_table.length-1);
173 		auto i = start;
174 		while (!Traits.equals(m_table[i].key, key)) {
175 			if (Traits.equals(m_table[i].key, Traits.clearValue)) return size_t.max;
176 			if (++i >= m_table.length) i -= m_table.length;
177 			if (i == start) return size_t.max;
178 		}
179 		return i;
180 	}
181 	
182 	private size_t findInsertIndex(in Key key)
183 	const {
184 		auto hash = m_hasher(*cast(Key*) &key);
185 		size_t target = hash & (m_table.length-1);
186 		auto i = target;
187 		while (!Traits.equals(m_table[i].key, Traits.clearValue) && !Traits.equals(m_table[i].key, key)) {
188 			if (++i >= m_table.length) i -= m_table.length;
189 			assert (i != target, "No free bucket found, HashMap full!?");
190 		}
191 		return i;
192 	}
193 	
194 	private void grow(size_t amount)
195 	{
196 		auto newsize = m_length + amount;
197 		if (newsize < (m_table.length*2)/3) return;
198 		auto newcap = m_table.length ? m_table.length : 16;
199 		while (newsize >= (newcap*2)/3) newcap *= 2;
200 		resize(newcap);
201 	}
202 
203 	private void resize(size_t new_size)
204 	{
205 		assert(!m_resizing);
206 		m_resizing = true;
207 		scope(exit) m_resizing = false;
208 		
209 		if (!m_hasher) {
210 			setupHasher();
211 		}
212 
213 		// logTrace("Resizing ", Key.stringof, " : ", Value.stringof, " : ", cast(void*)&this, " from ", m_table.length, " to: ", new_size);
214 
215 		uint pot = 0;
216 		while (new_size > 1) pot++, new_size /= 2;
217 		new_size = 1 << pot;
218 		auto old_size = m_table.length;
219 		assert(new_size > old_size); // TODO: Allow hashmap to become smaller?
220 		auto oldtable = m_table;
221 		m_table = allocArray!(TableEntry, ALLOC)(new_size);
222 
223 		/// fixme: Use initializeAll ?
224 		/// 
225 		foreach (ref el; m_table) {
226 			static if (is(Key == struct)) {
227 				emplace(cast(UnConst!Key*)&el.key);
228 				static if (Traits.clearValue !is Key.init)
229 					el.key = cast(UnConst!Key)Traits.clearValue;
230 			} else el.key = cast(UnConst!Key)Traits.clearValue;
231 			emplace(&el.value);
232 		}
233 		foreach (ref el; oldtable)
234 		if (!Traits.equals(el.key, Traits.clearValue)) {
235 			auto idx = findInsertIndex(el.key);
236 			(cast(ubyte[])(&m_table[idx])[0 .. 1])[] = (cast(ubyte[])(&el)[0 .. 1])[];
237 		}
238 
239 		if (oldtable) freeArray!(TableEntry, ALLOC)(oldtable, 0);
240 		logTrace("Now have ", m_table.length);
241 	}
242 
243 	void setupHasher() {
244 
245 		
246 		static if ((__traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toArray") ) ||
247 			__traits(hasMember, Key, "toArray"))
248 		{
249 			m_hasher = (Key k) {
250 				import std.typecons : scoped;
251 				import memutils.vector : Array;
252 				Array!ubyte s = k.toArray();
253 				size_t hash = hashOf(s[], 0);
254 				return hash;
255 			};
256 		}
257 		else static if ((__traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toVector") ) ||
258 			__traits(hasMember, Key, "toVector"))
259 		{
260 			m_hasher = (Key k) {
261 				import std.typecons : scoped;
262 				import memutils.vector : Vector;
263 				Vector!char s = k.toVector();
264 				size_t hash = hashOf(s[], 0);
265 				return hash;
266 			};
267 		}
268 		else static if (( __traits(hasMember, Key, "isRefCounted") && __traits(hasMember, typeof(*(Key())), "toString") ) ||
269 			__traits(hasMember, Key, "toString"))
270 		{
271 			m_hasher = (Key k) {
272 				string s = k.toString();
273 				size_t hash = typeid(string).getHash(&s);
274 				// logTrace("string ", s, " hash:" , hash);
275 				return hash;
276 			};
277 		}
278 		
279 		else static if (__traits(compiles, (){ Key t; size_t hash = t.toHash(); }())) {
280 			static if (isPointer!Key || is(Unqual!Key == class)) m_hasher = k => k ? k.toHash() : 0;
281 			else m_hasher = k => k.toHash();
282 		} else static if (__traits(compiles, (){ Key t; size_t hash = t.toHashShared(); }())) {
283 			static if (isPointer!Key || is(Unqual!Key == class)) m_hasher = k => k ? k.toHashShared() : 0;
284 			else m_hasher = k => k.toHashShared();
285 		} 
286 		else static if (__traits(hasMember, Key, "isRefCounted")) {
287 			m_hasher = (k) { return typeid(typeof(*(Key()))).getHash(&k); };
288 		}
289 		else {
290 			m_hasher = (k) { return typeid(Key).getHash(&k); };
291 		}
292 	}
293 }
294 
295 
296 struct DefaultHashMapTraits(Key) {
297 	enum clearValue = Key.init;
298 	static bool equals(in Key a, in Key b)
299 	{
300 		static if (__traits(hasMember, Key, "isRefCounted") && 
301 			is (typeof(*(Key())) == class) && 
302 			__traits(compiles, "bool c = a.opEquals(*b);"))
303 		{
304 			if (!a && b) {
305 				return b.opEquals(*a);
306 			}
307 			else if (a && !b) {
308 				return a.opEquals(*b);
309 			}
310 			else if (a && b)
311 			{
312 				return a.opEquals(*b);
313 			}
314 			else {
315 				return true;
316 			}
317 		}
318 		else static if (__traits(hasMember, Key, "isRefCounted") && is (typeof(*(Key())) == class)) {
319 			return *a is *b;
320 		}
321 		else static if (isPointer!Key) {
322 			return a is b;
323 		}
324 		else {
325 			return a == b;
326 		}
327 	}
328 }