1 /** Extensions to std.algorithm.searching.
2     Copyright: Per Nordlöw 2022-.
3     License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4     Authors: $(WEB Per Nordlöw)
5 */
6 module nxt.searching_ex;
7 
8 /** This function returns the index of the `value` if it exist among `values`,
9     `size_t.max` otherwise.
10 
11     TODO: Should we extend to isRandomAccessRange support? In that case we don't
12     get static array support by default.
13 */
14 size_t binarySearch(R, E)(const R[] values, in E value)
15     if (is(typeof(values[0].init == E.init))) // TODO: SortedRange support
16 {
17     // value is not in the array if the array is empty
18     if (values.length == 0) { return typeof(return).max; }
19 
20     immutable mid = values.length / 2; // mid offset
21     if (value == values[mid])
22         return mid; // direct hit
23     else if (value < values[mid])
24         return binarySearch(values[0 .. mid], value); // recurse left
25     else
26     {
27         const index = binarySearch(values[mid + 1 .. $], value); // recurse right
28         if (index != typeof(return).max)
29             return index + mid + 1; // adjust the index; it is 0-based in the right-hand side slice.
30         return index;
31     }
32 }
33 
34 ///
35 @safe pure nothrow @nogc unittest
36 {
37     const int[9] x = [1, 3, 5, 6, 8, 9, 10, 13, 15];
38     assert(x.binarySearch(0) == size_t.max);
39     assert(x.binarySearch(1) == 0);
40     assert(x.binarySearch(2) == size_t.max);
41     assert(x.binarySearch(3) == 1);
42     assert(x.binarySearch(4) == size_t.max);
43     assert(x.binarySearch(5) == 2);
44     assert(x.binarySearch(6) == 3);
45     assert(x.binarySearch(7) == size_t.max);
46     assert(x.binarySearch(8) == 4);
47     assert(x.binarySearch(9) == 5);
48     assert(x.binarySearch(10) == 6);
49     assert(x.binarySearch(11) == size_t.max);
50     assert(x.binarySearch(12) == size_t.max);
51     assert(x.binarySearch(13) == 7);
52     assert(x.binarySearch(14) == size_t.max);
53     assert(x.binarySearch(15) == 8);
54 }
55 
56 import std.range : ElementType, SearchPolicy, SortedRange;
57 
58 /** Same as `range.contains()` but also outputs `index` where last occurrence of
59     `key` is either currently stored (if `true` is returned) or should be stored
60     (if `false` is returned) in order to preserve sortedness of `range`.
61 
62     The elements of `range` are assumed to be sorted in default (ascending)
63     order.
64 
65     TODO: Move to member of `SortedRange` either as a new name or as an
66     `contains`-overload take an extra `index` as argument.
67  */
68 bool containsStoreIndex(SearchPolicy sp = SearchPolicy.binarySearch, R, V)
69                        (R range, V value, out size_t index)
70     if (is(typeof(ElementType!R.init == V.init)) &&
71         is(R == SortedRange!(_), _)) // TODO: check for comparsion function
72 {
73     // TODO: should we optimize for this case?
74     // if (range.empty)
75     // {
76     //     index = 0;
77     //     return false;           // no hit
78     // }
79     index = range.length - range.upperBound!sp(value).length; // always larger than zero
80     if (index >= 1 && range[index - 1] == value)
81     {
82         --index;                             // make index point to last occurrence of `value`
83         assert(range.contains(value)); // assert same behaviour as existing contains
84         return true;
85     }
86     assert(!range.contains(value)); // assert same behaviour as existing contains
87     return false;
88 }
89 
90 ///
91 @safe pure nothrow @nogc unittest
92 {
93     const int[0] x;
94     size_t index;
95     import std.range : assumeSorted;
96     assert(!x[].assumeSorted.containsStoreIndex(int.min, index) && index == 0);
97     assert(!x[].assumeSorted.containsStoreIndex(-1,      index) && index == 0);
98     assert(!x[].assumeSorted.containsStoreIndex(0,       index) && index == 0);
99     assert(!x[].assumeSorted.containsStoreIndex(1,       index) && index == 0);
100     assert(!x[].assumeSorted.containsStoreIndex(int.max, index) && index == 0);
101 }
102 
103 ///
104 @safe pure nothrow @nogc unittest
105 {
106     const int[2] x = [1, 3];
107     size_t index;
108     import std.range : assumeSorted;
109     assert(!x[].assumeSorted.containsStoreIndex(int.min, index) && index == 0);
110     assert(!x[].assumeSorted.containsStoreIndex(-1,      index) && index == 0);
111     assert(!x[].assumeSorted.containsStoreIndex(0,       index) && index == 0);
112     assert( x[].assumeSorted.containsStoreIndex(1,       index) && index == 0);
113     assert(!x[].assumeSorted.containsStoreIndex(2,       index) && index == 1);
114     assert( x[].assumeSorted.containsStoreIndex(3,       index) && index == 1);
115     assert(!x[].assumeSorted.containsStoreIndex(4,       index) && index == 2);
116     assert(!x[].assumeSorted.containsStoreIndex(5,       index) && index == 2);
117     assert(!x[].assumeSorted.containsStoreIndex(int.max, index) && index == 2);
118 }