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 }