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