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