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 }