1 /** Probing algorithms used by for instance hash tables.
2  */
3 module nxt.probing;
4 
5 import std.functional : unaryFun, binaryFun;
6 
7 /** Search for a key in `haystack` matching predicate `pred` starting at `index`
8  * in steps of triangular numbers, 0,1,3,6,10,15,21, ... .
9  *
10  * If `assumeNonFullHaystack` is `true` it is assumed that at least one element
11  * in `haystack` matches `pred`, thereby enabling sentinel-based probing. Such
12  * probing doesn't require in-loop range checking via `indexIncrement !=
13  * haystack.length` and can be made faster.
14  *
15  * Returns: index into `haystack` upon hit, `haystack.length` upon miss.
16  *
17  * Note: `haystack.length` must be a power of two (or 1 or zero).
18  *
19  * See_Also: https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/
20  */
21 size_t triangularProbeFromIndex(alias pred,
22 								bool assumeNonFullHaystack = false,
23 								T)(const scope T[] haystack, size_t index)
24 if (is(typeof(unaryFun!pred(T.init))) ||
25 	is(typeof(binaryFun!pred(size_t.init, T.init)))) {
26 	immutable mask = haystack.length - 1;
27 	assert((~mask ^ mask) == typeof(return).max); // std.math.isPowerOf2(haystack.length)
28 
29 	static if (assumeNonFullHaystack)
30 		assert(haystack.length != 0, "haystack cannot be empty");
31 
32 	// search using triangular numbers as increments
33 	size_t indexIncrement = 0;
34 	while (true) {
35 		static if (assumeNonFullHaystack)
36 			assert(indexIncrement != haystack.length,
37 				   "no element in `haystack` matches `pred`, cannot used sentinel-based probing");
38 		else
39 			if (indexIncrement == haystack.length) { return haystack.length; }
40 
41 		static if (is(typeof(unaryFun!pred(T.init)))) {
42 			if (unaryFun!pred(haystack[index]))
43 				return index;
44 		} else static if (is(typeof(binaryFun!pred(size_t.min, T.init)))) {
45 			if (binaryFun!pred(index, haystack[index]))
46 				return index;
47 		} else
48 			static assert(0, "Unsupported predicate of type " ~ typeof(pred).stringof);
49 
50 		indexIncrement += 1;
51 		index = (index + indexIncrement) & mask; // next triangular number modulo length
52 	}
53 }
54 
55 /** Search for a key in `haystack` matching hit predicate `hitPred` and hole/tombstone
56  * predicate `holePred` starting at `index` in steps of triangular numbers,
57  * 0,1,3,6,10,15,21, ... .
58  *
59  * If `assumeNonFullHaystack` is `true` it is assumed that at least one element
60  * in `haystack` matches `pred`, thereby enabling sentinel-based probing. Such
61  * probing doesn't require in-loop range checking via `indexIncrement !=
62  * haystack.length` and can be made faster.
63  *
64  * Returns: index into `haystack` upon hit, `haystack.length` upon miss.
65  *
66  * Note: `haystack.length` must be a power of two (or 1 or zero).
67  *
68  * See_Also: https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/
69  */
70 size_t triangularProbeFromIndexIncludingHoles(alias hitPred,
71 											  alias holePred,
72 											  bool assumeNonFullHaystack = false,
73 											  T)(const scope T[] haystack,
74 												 size_t index,
75 												 ref size_t holeIndex) // first hole index
76 if ((is(typeof(unaryFun!hitPred(T.init))) ||
77 	 is(typeof(binaryFun!hitPred(size_t.init, T.init)))) ||
78 	(is(typeof(unaryFun!holePred(T.init))) ||
79 	 is(typeof(binaryFun!holePred(size_t.init, T.init))))) {
80 	immutable mask = haystack.length - 1;
81 	assert((~mask ^ mask) == typeof(return).max); // std.math.isPowerOf2(haystack.length)
82 
83 	static if (assumeNonFullHaystack)
84 		assert(haystack.length != 0, "haystack cannot be empty");
85 
86 	// search using triangular numbers as increments
87 	size_t indexIncrement = 0;
88 	while (true) {
89 		static if (assumeNonFullHaystack)
90 			assert(indexIncrement != haystack.length,
91 				   "no element in `haystack` matches `hitPred`, cannot used sentinel-based probing");
92 		else
93 			if (indexIncrement == haystack.length)
94 				return haystack.length;
95 
96 		static if (is(typeof(unaryFun!hitPred(T.init)))) {
97 			if (unaryFun!hitPred(haystack[index]))
98 				return index;
99 		} else static if (is(typeof(binaryFun!hitPred(size_t.min, T.init)))) {
100 			if (binaryFun!hitPred(index, haystack[index]))
101 				return index;
102 		} else
103 			static assert(0, "Unsupported hit predicate of type " ~ typeof(hitPred).stringof);
104 
105 		if (holeIndex == size_t.max) { // if not yet initialized
106 			static if (is(typeof(unaryFun!holePred(T.init)))) {
107 				if (unaryFun!holePred(haystack[index]))
108 					holeIndex = index;
109 			} else static if (is(typeof(binaryFun!holePred(size_t.min, T.init)))) {
110 				if (binaryFun!holePred(index, haystack[index]))
111 					holeIndex = index;
112 			}
113 		}
114 
115 		indexIncrement += 1;
116 		index = (index + indexIncrement) & mask; // next triangular number modulo length
117 	}
118 }
119 
120 size_t triangularProbeCountFromIndex(alias pred,
121 									 T)(const scope T[] haystack, size_t index)
122 if (is(typeof(unaryFun!pred(T.init)))) {
123 	immutable mask = haystack.length - 1;
124 	assert((~mask ^ mask) == typeof(return).max); // std.math.isPowerOf2(haystack.length)
125 
126 	// search using triangular numbers as increments
127 	size_t indexIncrement = 0;
128 	while (true) {
129 		if (indexIncrement == haystack.length)
130 			return indexIncrement + 1;
131 		if (unaryFun!pred(haystack[index]))
132 			return indexIncrement + 1;
133 		indexIncrement += 1;
134 		index = (index + indexIncrement) & mask; // next triangular number modulo length
135 	}
136 }
137 
138 /// empty case
139 pure nothrow @safe unittest {
140 	import std.typecons : Nullable;
141 	alias T = Nullable!int;
142 
143 	immutable length = 0;
144 	immutable hitKey = T(42); // key to store
145 	auto haystack = new T[length];
146 
147 	assert(haystack.triangularProbeFromIndex!((T element) => (element is hitKey ||
148 															  element.isNull))(0) == haystack.length);
149 	assert(haystack.triangularProbeFromIndex!((size_t index, T element) => true)(0) == 0);
150 	assert(haystack.triangularProbeFromIndex!((size_t index, T element) => false)(0) == haystack.length);
151 }
152 
153 /// generic case
154 pure nothrow @safe unittest {
155 	import std.typecons : Nullable;
156 	alias T = Nullable!int;
157 
158 	foreach (immutable lengthPower; 0 .. 20) {
159 		immutable length = 2^^lengthPower;
160 
161 		immutable hitKey = T(42);  // key to store
162 		immutable missKey = T(43); // other key not present
163 
164 		auto haystack = new T[length];
165 		haystack[] = T(17);	 // make haystack full
166 		haystack[$/2] = hitKey;
167 
168 		alias elementHitPredicate = element => (element is hitKey || element.isNull);
169 		alias elementMissPredicate = element => (element is missKey || element.isNull);
170 
171 		// key hit
172 		assert(haystack.triangularProbeFromIndex!(elementHitPredicate)(lengthPower) != haystack.length);
173 
174 		// key miss
175 		assert(haystack.triangularProbeFromIndex!(elementMissPredicate)(lengthPower) == haystack.length);
176 	}
177 }
178 
179 @trusted pure unittest {
180 	class C { int value; }
181 	C x;
182 	C y = cast(C)((cast(size_t*)null) + 1); // indicates a lazily deleted key
183 	struct S {
184 		/+ TODO: make these work: +/
185 		// enum C hole1 = cast(C)((cast(size_t*)null) + 1);
186 		// static immutable C hole2 = cast(C)((cast(size_t*)null) + 1);
187 	}
188 }