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