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