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 }