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