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