1 /// Based on https://github.com/attractivechaos/klib commit 8ffbbdb2bf80ee84ba089dbabd5fa2420ee9aefe dated 2019-03-24
2 module intervaltree.khash;
3 
4 import std.traits;	// isIntegral
5 
6 /*
7 #include <memory>
8 #include <functional>
9 #include <cstdlib> // for malloc() etc
10 #include <cstring> // for memset()
11 */
12 import core.stdc.stdlib;	// for malloc() etc
13 
14 import core.std.stdint;	// for uint32_t
15 
16 //FIXME: doesn't work for 64-bit integers
17 //#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
18 import intervaltree.roundup;
19 
20 pragma(inline, true) {
21 ///#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
22 auto __ac_isempty(T)(uint32_t* flag, T i) if (isIntegral!T)
23 { return ((flag[i>>4]>>((i&0xfU)<<1))&2); }
24 ///#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
25 auto __ac_isdel(T)(uint32_t* flag, T i) if (isIntegral!T)
26 { return ((flag[i>>4]>>((i&0xfU)<<1))&1); }
27 ///#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
28 auto __ac_isempty(T)(uint32_t* flag, T i) if (isIntegral!T)
29 { return ((flag[i>>4]>>((i&0xfU)<<1))&2); }
30 ///#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
31 auto __ac_isdel(T)(uint32_t* flag, T i) if (isIntegral!T)
32 { return ((flag[i>>4]>>((i&0xfU)<<1))&1); }
33 ///#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
34 auto __ac_iseither(T)(uint32_t* flag, T i) if (isIntegral!T)
35 { return ((flag[i>>4]>>((i&0xfU)<<1))&3); }
36 ///#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
37 void __ac_set_isdel_false(T)(uint32_t* flag, T i) if (isIntegral!T)
38 { (flag[i>>4]&=~(1uL<<((i&0xfU)<<1))); }
39 ///#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
40 void __ac_set_isempty_false(T)(uint32_t* flag, T i) if (isIntegral!T)
41 { (flag[i>>4]&=~(2uL<<((i&0xfU)<<1))); }
42 ///#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
43 void __ac_set_isboth_false(T)(uint32_t* flag, T i) if (isIntegral!T)
44 { (flag[i>>4]&=~(3uL<<((i&0xfU)<<1))); }
45 ///#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
46 void __ac_set_isdel_true(T)(uint32_t* flag, T i) if (isIntegral!T)
47 { (flag[i>>4]|=1uL<<((i&0xfU)<<1)); }
48 } // pragma inline
49 
50 /// If < 16 return 1, othewise return m / 16
51 /// #define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
52 string __ac_fsize(string m)	{ return "(" ~ m ~ "< 16? 1 : " ~ m ~ ">>4)"; }
53 
54 ///template<class T, class Hash, class Eq = std::equal_to<T>, typename khint_t = uint32_t>
55 class KHash(T, hashfn, khint_t = uint32_t)
56 {
57 private:
58 	khint_t n_buckets, count, n_occupied, upper_bound;
59 	uint32_t *flags;
60 	T *keys;
61 public:
62 	~this()
63 	{
64 		free(flags);
65 		free(keys);
66 	}
67 	/// capacity (number of buckets)
68 	khint_t capacity(void) const { return n_buckets; }
69 	/// actual size (num objects)
70 	khint_t size(void) const { return count; }
71 	/// zero
72 	khint_t begin(void) const { return 0; }
73 	/// same as capacity / n.buckets
74 	khint_t end(void) const { return n_buckets; }
75 
76 	/// does object exist at this integer offset?
77 	void exist(khint_t x) const { return !__ac_iseither(flags, x); }
78 
79 	/// return bucket contents at integer offset by reference
80 	ref T at(khint_t x) { return keys[x]; } // validate
81 
82 	/// get integer offset of value for key
83 	khint_t get(ref const T key) const {
84 		if (n_buckets) {
85 			khint_t k, i, last, mask, step = 0;
86 			mask = n_buckets - 1;
87 			k = Hash()(key); i = k & mask;
88 			last = i;
89 			while (!__ac_isempty(flags, i) && (__ac_isdel(flags, i) || !Eq()(keys[i], key))) {
90 				i = (i + (++step)) & mask;
91 				if (i == last) return n_buckets;
92 			}
93 			return __ac_iseither(flags, i)? n_buckets : i;
94 		} else return 0;
95 	}
96 
97 	/// resize the hash table
98 	int resize(khint_t new_n_buckets) {
99 		uint32_t *new_flags = null;
100 		khint_t j = 1;
101 		{
102 			new_n_buckets = kroundup32(new_n_buckets);	// JSB: prior macro updates in place, my asm returns rounded value
103 			if (new_n_buckets < 4) new_n_buckets = 4;
104 			if (count >= (new_n_buckets>>1) + (new_n_buckets>>2)) j = 0;	/* requested count is too small */
105 			else { /* hash table count to be changed (shrink or expand); rehash */
106 				new_flags = cast(uint32_t*) malloc( mixin(__ac_fsize(new_n_buckets)) * sizeof(uint32_t));
107 				if (!new_flags) return -1;
108 				memset(new_flags, 0xaa, mixin(__ac_fsize(new_n_buckets)) * sizeof(uint32_t));
109 				if (n_buckets < new_n_buckets) {	/* expand */
110 					T *new_keys = (T*)std::realloc((void *)keys, new_n_buckets * sizeof(T));
111 					if (!new_keys) { std::free(new_flags); return -1; }
112 					keys = new_keys;
113 				} /* otherwise shrink */
114 			}
115 		}
116 		if (j) { /* rehashing is needed */
117 			for (j = 0; j != n_buckets; ++j) {
118 				if (__ac_iseither(flags, j) == 0) {
119 					T key = keys[j];
120 					khint_t new_mask;
121 					new_mask = new_n_buckets - 1;
122 					__ac_set_isdel_true(flags, j);
123 					while (1) { /* kick-out process; sort of like in Cuckoo hashing */
124 						khint_t k, i, step = 0;
125 						k = Hash()(key);
126 						i = k & new_mask;
127 						while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask;
128 						__ac_set_isempty_false(new_flags, i);
129 						if (i < n_buckets && __ac_iseither(flags, i) == 0) { /* kick out the existing element */
130 							{ T tmp = keys[i]; keys[i] = key; key = tmp; }
131 							__ac_set_isdel_true(flags, i); /* mark it as deleted in the old hash table */
132 						} else { /* write the element and jump out of the loop */
133 							keys[i] = key;
134 							break;
135 						}
136 					}
137 				}
138 			}
139 			if (n_buckets > new_n_buckets) /* shrink the hash table */
140 				keys = (T*)std::realloc((void *)keys, new_n_buckets * sizeof(T));
141 			free(flags); /* free the working space */
142 			flags = new_flags;
143 			n_buckets = new_n_buckets;
144 			n_occupied = count;
145 			upper_bound = (n_buckets>>1) + (n_buckets>>2);
146 		}
147 		return 0;
148 	}
149 
150 	/// put a new object in the hash table
151 	khint_t put(const T &key, int *ret) {
152 		khint_t x;
153 		if (n_occupied >= upper_bound) { /* update the hash table */
154 			if (n_buckets > (count<<1)) {
155 				if (resize(n_buckets - 1) < 0) { /* clear "deleted" elements */
156 					*ret = -1; return n_buckets;
157 				}
158 			} else if (resize(n_buckets + 1) < 0) { /* expand the hash table */
159 				*ret = -1; return n_buckets;
160 			}
161 		} /* TODO: to implement automatically shrinking; resize() already support shrinking */
162 		{
163 			khint_t k, i, site, last, mask = n_buckets - 1, step = 0;
164 			x = site = n_buckets; k = Hash()(key); i = k & mask;
165 			if (__ac_isempty(flags, i)) x = i; /* for speed up */
166 			else {
167 				last = i;
168 				while (!__ac_isempty(flags, i) && (__ac_isdel(flags, i) || !Eq()(keys[i], key))) {
169 					if (__ac_isdel(flags, i)) site = i;
170 					i = (i + (++step)) & mask;
171 					if (i == last) { x = site; break; }
172 				}
173 				if (x == n_buckets) {
174 					if (__ac_isempty(flags, i) && site != n_buckets) x = site;
175 					else x = i;
176 				}
177 			}
178 		}
179 		if (__ac_isempty(flags, x)) { /* not present at all */
180 			keys[x] = key;
181 			__ac_set_isboth_false(flags, x);
182 			++count; ++n_occupied;
183 			*ret = 1;
184 		} else if (__ac_isdel(flags, x)) { /* deleted */
185 			keys[x] = key;
186 			__ac_set_isboth_false(flags, x);
187 			++count;
188 			*ret = 2;
189 		} else *ret = 0; /* Don't touch keys[x] if present and not deleted */
190 		return x;
191 	}
192 
193 	/// remove the object at integer offset
194 	void del(khint_t x) {
195 		if (x != n_buckets && !__ac_iseither(flags, x)) {
196 			__ac_set_isdel_true(flags, x);
197 			--count;
198 		}
199 	}//ok
200 }