1 /** Preditive Search
2  *
3  * A search algorithm based on Binary search which tries to reduce the number of memory accesses.
4  * If, on top of knowing that the input is sorted, we assume that it is distributed uniformally,
5  * we can then use the first and last object in the range to try and predict where the item we
6  * are looking for would be. This means that we would be doing less memory accesses.
7  *
8  * The formula for the prediction is `indexOf(min) + (needle - min)/(max - min) * (indexOf(max) - indexOf(min))`
9  */
10 module predictivesearch;
11 
12 import std.traits;
13 import std.conv;
14 import std.typecons;
15 
16 /// find needle in haystack using predictive search
17 Nullable!size_t find(R, T)(R haystack, T needle)
18 if(is(ReturnType!((R r, T t, size_t index) => r[index] == t) : bool) &&
19    is(ReturnType!((R r, T t) => r[0] < t) : bool) &&
20    is(ReturnType!((R r, T t) => r[0] > t) : bool) &&
21    is(ReturnType!((R r) => r.length) : size_t) &&
22    is(ReturnType!((R r, size_t i1, size_t i2) =>
23       cast(size_t)(i2 + (r[0] - r[1]).to!double / (r[2] - r[1]).to!double * (i1 - i2))) : size_t))
24 {
25    alias n_size_t = Nullable!size_t;
26 
27    if(haystack.length == 0) return n_size_t.init;
28    auto min = haystack[0];
29    size_t minIndex = 0;
30    if(needle == min) return n_size_t(minIndex);
31    if(min > needle) return n_size_t.init;
32    
33    auto max = haystack[haystack.length - 1]; // using length rather than $ to make the interface less restrictive
34 
35    size_t maxIndex = haystack.length - 1;
36 
37    if(needle == max) return n_size_t(maxIndex);
38    if(max < needle) return n_size_t.init;
39 
40    while(max != min)
41    {
42       size_t prediction = cast(size_t)
43          (minIndex + (needle - min).to!double / (max - min).to!double * (maxIndex - minIndex));
44       // max and min should already have been tested at this point,
45       // if predition truncated down to one of them, simply move them in by one.
46       if(prediction == minIndex)
47       {
48          minIndex++;
49          min = haystack[minIndex];
50          continue;
51       }
52       if(prediction == maxIndex) // should only happen if max < 0
53       {
54          maxIndex--;
55          max = haystack[maxIndex];
56          continue;
57       }
58 
59       auto actual = haystack[prediction];
60       if(actual == needle) return n_size_t(prediction);
61       if(actual < needle)
62       {
63          min = actual;
64          minIndex = prediction;
65          continue;
66       }
67       max = actual;
68       maxIndex = prediction;
69    }
70    return n_size_t.init;
71 }
72 
73 ///
74 unittest
75 {
76    auto array = [1, 2, 3, 4];
77    auto res = find(array, 1);
78    assert(!res.isNull);
79    assert(res.get == 0);
80    res = find(array, 4UL);
81    assert(!res.isNull);
82    assert(res.get == 3);
83    assert(find(array, 5).isNull);
84 }
85 
86 ///
87 unittest
88 {
89    auto array = [4, 5, 41, 44, 45, 48, 60, 72, 76,
90                   93, 102, 130, 132, 155, 174, 213, 230,
91                   247, 258, 266, 266, 272, 282, 304, 315,
92                   325, 334, 344, 350, 357, 369, 372, 376,
93                   382, 386, 426, 429, 454, 466, 472, 476,
94                   477, 478, 484, 508, 529, 539, 551, 571,
95                   574, 575, 581, 596, 619, 624, 629, 634,
96                   638, 653, 679, 680, 684, 684, 688, 698,
97                   729, 731, 746, 761, 762, 769, 779, 791,
98                   800, 806, 808, 818, 824, 828, 831, 833,
99                   835, 846, 857, 864, 867, 873, 885, 898,
100                   900, 918, 926, 936, 938, 941, 960, 977,
101                   981, 986, 989];
102    auto res = find(array, 155);
103    assert(!res.isNull);
104    assert(res.get == 13);
105    assert(find(array, 819).isNull);
106 }