1 /**
2 Copyright: Copyright (c) 2016-2017 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.container.hashmap;
7 
8 import voxelman.log;
9 import std.stdio;
10 import std..string;
11 import std.experimental.allocator.gc_allocator;
12 import voxelman.math : nextPOT;
13 
14 struct HashMap(Key, Value, Key nullKey = Key.max, A = GCAllocator)
15 {
16 	Key[] keys;
17 	Value[] values;
18 	size_t length;
19 
20 	private bool resizing;
21 
22 	alias allocator = A.instance;
23 
24 	this(ubyte[] array, size_t length) {
25 		assert(array.length % (Key.sizeof + Value.sizeof) == 0);
26 		size_t size = array.length / (Key.sizeof + Value.sizeof);
27 		keys = cast(Key[])array[0..Key.sizeof * size];
28 		values = cast(Value[])array[Key.sizeof * size..$];
29 		this.length = length;
30 	}
31 
32 	ubyte[] getTable() {
33 		return (cast(ubyte[])keys).ptr[0..(Key.sizeof + Value.sizeof) * keys.length];
34 	}
35 
36 	@property size_t capacity() const { return keys.length; }
37 	@property bool empty() const { return length == 0; }
38 
39 	void remove(Key key) {
40 		auto idx = findIndex(key);
41 		if (idx == size_t.max) return;
42 		auto i = idx;
43 		while (true)
44 		{
45 			keys[i] = nullKey;
46 
47 			size_t j = i, r;
48 			do {
49 				if (++i >= keys.length) i -= keys.length;
50 				if (keys[i] == nullKey)
51 				{
52 					--length;
53 					return;
54 				}
55 				r = keys[i] & (keys.length-1);
56 			}
57 			while ((j<r && r<=i) || (i<j && j<r) || (r<=i && i<j));
58 			keys[j] = keys[i];
59 			values[j] = values[i];
60 		}
61 	}
62 
63 	Value get(Key key, Value default_value = Value.init) {
64 		auto idx = findIndex(key);
65 		if (idx == size_t.max) return default_value;
66 		return values[idx];
67 	}
68 
69 	Value* getOrCreate(Key key, Value defVal = Value.init) {
70 		auto idx = findIndex(key);
71 		if (idx == size_t.max) return assignValue(key, defVal);
72 		return &values[idx];
73 	}
74 
75 	void clear() {
76 		keys[] = nullKey;
77 		length = 0;
78 	}
79 
80 	void opIndexAssign(Value value, Key key) {
81 		assignValue(key, value);
82 	}
83 
84 	private Value* assignValue(Key key, Value value) {
85 		grow(1);
86 		auto i = findInsertIndex(key);
87 		if (keys[i] != key) ++length;
88 
89 		keys[i] = key;
90 		values[i] = value;
91 		return &values[i];
92 	}
93 
94 	ref inout(Value) opIndex(Key key) inout {
95 		auto idx = findIndex(key);
96 		assert (idx != size_t.max, "Accessing non-existent key.");
97 		return values[idx];
98 	}
99 
100 	inout(Value)* opBinaryRight(string op)(Key key) inout if (op == "in") {
101 		auto idx = findIndex(key);
102 		if (idx == size_t.max) return null;
103 		return &values[idx];
104 	}
105 
106 	int opApply(scope int delegate(ref Value) del) {
107 		foreach (i; 0 .. keys.length)
108 			if (keys[i] != nullKey)
109 				if (auto ret = del(values[i]))
110 					return ret;
111 		return 0;
112 	}
113 
114 	int opApply(scope int delegate(in ref Value) del) const {
115 		foreach (i; 0 .. keys.length)
116 			if (keys[i] != nullKey)
117 				if (auto ret = del(values[i]))
118 					return ret;
119 		return 0;
120 	}
121 
122 	int opApply(scope int delegate(Key, ref Value) del) {
123 		foreach (i; 0 .. keys.length)
124 			if (keys[i] != nullKey)
125 				if (auto ret = del(keys[i], values[i]))
126 					return ret;
127 		return 0;
128 	}
129 
130 	int opApply(scope int delegate(in ref Key, in ref Value) del) const {
131 		foreach (i; 0 .. keys.length)
132 			if (keys[i] != nullKey)
133 				if (auto ret = del(keys[i], values[i]))
134 					return ret;
135 		return 0;
136 	}
137 
138 	void reserve(size_t amount) {
139 		auto newcap = ((length + amount) * 3) / 2;
140 		resize(newcap);
141 	}
142 
143 	void shrink() {
144 		auto newcap = length * 3 / 2;
145 		resize(newcap);
146 	}
147 
148 	void printStats() {
149 		writefln("cap %s len %s", capacity, length);
150 	}
151 
152 	private size_t findIndex(Key key) const {
153 		if (length == 0) return size_t.max;
154 		size_t start = key & (keys.length-1);
155 		auto i = start;
156 		while (keys[i] != key) {
157 			if (keys[i] == nullKey) return size_t.max;
158 			if (++i >= keys.length) i -= keys.length;
159 			if (i == start) return size_t.max;
160 		}
161 		return i;
162 	}
163 
164 	private size_t findInsertIndex(Key key) const {
165 		size_t target = key & (keys.length-1);
166 		auto i = target;
167 		while (keys[i] != nullKey && keys[i] != key) {
168 			if (++i >= keys.length) i -= keys.length;
169 			assert (i != target, "No free bucket found, HashMap full!?");
170 		}
171 		return i;
172 	}
173 
174 	private void grow(size_t amount) {
175 		auto newsize = length + amount;
176 		if (newsize < (keys.length*2)/3) return;
177 		auto newcap = keys.length ? keys.length : 1;
178 		while (newsize >= (newcap*2)/3) newcap *= 2;
179 		resize(newcap);
180 	}
181 
182 	private void resize(size_t newSize)
183 	{
184 		assert(!resizing);
185 		resizing = true;
186 		scope(exit) resizing = false;
187 
188 		newSize = nextPOT(newSize);
189 
190 		auto oldKeys = keys;
191 		auto oldValues = values;
192 
193 		if (newSize) {
194 			void[] array = allocator.allocate((Key.sizeof + Value.sizeof) * newSize);
195 			keys = cast(Key[])(array[0..Key.sizeof * newSize]);
196 			values = cast(Value[])(array[Key.sizeof * newSize..$]);
197 			//infof("%s %s %s", array.length, keys.length, values.length);
198 			keys[] = nullKey;
199 			foreach (i, ref key; oldKeys) {
200 				if (key != nullKey) {
201 					auto idx = findInsertIndex(key);
202 					keys[idx] = key;
203 					values[idx] = oldValues[i];
204 				}
205 			}
206 		} else {
207 			keys = null;
208 			values = null;
209 		}
210 
211 		if (oldKeys) {
212 			void[] arr = (cast(void[])oldKeys).ptr[0..(Key.sizeof + Value.sizeof) * newSize];
213 			allocator.deallocate(arr);
214 		}
215 	}
216 
217 	void toString()(scope void delegate(const(char)[]) sink)
218 	{
219 		import std.format : formattedWrite;
220 		sink.formattedWrite("[",);
221 		foreach(key, value; this)
222 		{
223 			sink.formattedWrite("%s:%s, ", key, value);
224 		}
225 		sink.formattedWrite("]");
226 	}
227 }
228 
229 /*
230 unittest {
231 	BlockEntityMap map;
232 
233 	foreach (ushort i; 0 .. 100) {
234 		map[i] = i;
235 		assert(map.length == i+1);
236 	}
237 	map.printStats();
238 
239 	foreach (ushort i; 0 .. 100) {
240 		auto pe = i in map;
241 		assert(pe !is null && *pe == i);
242 		assert(map[i] == i);
243 	}
244 	map.printStats();
245 
246 	foreach (ushort i; 0 .. 50) {
247 		map.remove(i);
248 		assert(map.length == 100-i-1);
249 	}
250 	map.shrink();
251 	map.printStats();
252 
253 	foreach (ushort i; 50 .. 100) {
254 		auto pe = i in map;
255 		assert(pe !is null && *pe == i);
256 		assert(map[i] == i);
257 	}
258 	map.printStats();
259 
260 	foreach (ushort i; 50 .. 100) {
261 		map.remove(i);
262 		assert(map.length == 100-i-1);
263 	}
264 	map.printStats();
265 	map.shrink();
266 	map.printStats();
267 	map.reserve(100);
268 	map.printStats();
269 }*/
270 
271 unittest {
272 	ushort[] keys = [140,268,396,524,652,780,908,28,156,284,
273 		412,540,668,796,924,920,792,664,536,408,280,152,24];
274 	HashMap!(ushort, ulong) map;
275 
276 	foreach (i, ushort key; keys) {
277 		//writefln("set1 %s %s", map, map.length);
278 		map[key] = key;
279 		//writefln("set2 %s %s", map, map.length);
280 		assert(map.length == i+1, format("%s %s", i+1, map.length));
281 		assert(key in map && map[key] == key);
282 		assert(map.get(key, 0) == key);
283 	}
284 }