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