1 /// Returns an `optional` index of an element.
2 module ddash.algorithm.index;
3 
4 ///
5 unittest {
6     import optional: some, none;
7 
8     auto arr1 = [1, 2, 3, 4];
9 
10     assert(arr1.indexWhere!(a => a % 2 == 0) == some(1));
11     assert(arr1.lastIndexWhere!(a => a % 2 == 0) == some(3));
12 
13     assert(arr1.indexWhere!(a => a == 5) == none);
14     assert(arr1.lastIndexWhere!(a => a % 2 == 0)(2) == some(1));
15 
16     auto arr2 = [1, 2, 1, 2];
17 
18     assert(arr2.indexOf(2) == some(1));
19 }
20 
21 
22 import ddash.common;
23 
24 /**
25     Returns `optional` index of the first element predicate returns true for.
26 
27     Params:
28         pred = unary function that returns true for some element
29         range = the input range to search
30         fromIndex = which index to start searching from
31 
32     Returns:
33         `some!size_t` or `none` if no element was found
34 
35     Since:
36         0.0.1
37 */
38 auto indexWhere(alias pred, Range)(Range range, size_t fromIndex = 0)
39 if (from!"std.range".isInputRange!Range
40     && from!"bolts.traits".isUnaryOver!(pred, from!"std.range".ElementType!Range))
41 {
42     import std.range: drop;
43     import std.functional: unaryFun;
44     import ddash.algorithm.internal: countUntil;
45     auto r = range.drop(fromIndex);
46     return r.countUntil!((a, b) => unaryFun!pred(a))(r) + fromIndex;
47 }
48 
49 ///
50 unittest {
51     import optional: some, none;
52     assert([1, 2, 3, 4].indexWhere!(a => a % 2 == 0) == some(1));
53     assert([1, 2, 3, 4].indexWhere!(a => a == 5) == none);
54     assert([1, 2, 3, 4].indexWhere!(a => a % 2 == 0)(2) == some(3));
55 }
56 
57 /**
58     Returns `optional` index of the last element predicate returns true for.
59 
60     Params:
61         pred = unary function that returns true for some element
62         range = the input range to search
63         fromIndex = which index from the end to start searching from
64 
65     Returns:
66         `some!size_t` or `none` if no element was found
67 
68     Since:
69         0.0.1
70 */
71 auto lastIndexWhere(alias pred, Range)(Range range, size_t fromIndex = 0)
72 if (from!"std.range".isBidirectionalRange!Range
73     && from!"bolts.traits".isUnaryOver!(pred, from!"std.range".ElementType!Range))
74 {
75     import std.range: retro, walkLength;
76     import ddash.range: withFront;
77     return range
78         .retro
79         .indexWhere!pred(fromIndex)
80         .withFront!(a => range.walkLength - a - 1);
81 }
82 
83 ///
84 unittest {
85     import optional: some, none;
86     assert([1, 2, 3, 4].lastIndexWhere!(a => a % 2 == 0) == some(3));
87     assert([1, 2, 3, 4].lastIndexWhere!(a => a == 5) == none);
88     assert([1, 2, 3, 4].lastIndexWhere!(a => a % 2 == 0)(2) == some(1));
89 }
90 
91 /**
92     Finds the first element in a range that equals some value
93 
94     Params:
95         range = an input range
96         value = value to search for
97         fromIndex = which index to start searching from
98 
99     Returns:
100         An `Optional!T`
101 
102     Since:
103         0.0.1
104 */
105 auto indexOf(Range, T)(Range range, T value, size_t fromIndex = 0) if (from!"std.range".isInputRange!Range) {
106     import std.range: drop;
107     import ddash.algorithm.internal: countUntil;
108     import ddash.range: withFront;
109     return range
110         .drop(fromIndex)
111         .countUntil(value)
112         .withFront!(a => a + fromIndex);
113 }
114 
115 ///
116 unittest {
117     import optional;
118     assert([1, 2, 1, 2].indexOf(2) == some(1));
119     assert([1, 2, 1, 2].indexOf(2, 2) == some(3));
120     assert([1, 2, 1, 2].indexOf(3) == none);
121 }
122 
123 /**
124     Finds the first element in a range that equals some value
125 
126     Params:
127         range = an input range
128         value = value to search for
129         fromIndex = which index from the end to start searching from
130 
131     Returns:
132         An `optional!T`
133 
134     Since:
135         0.0.1
136 */
137 auto lastIndexOf(Range, T)(Range range, T value, size_t fromIndex = 0) if (from!"std.range".isBidirectionalRange!Range) {
138     import std.range: retro, walkLength;
139     import ddash.algorithm: indexOf;
140     import ddash.range: withFront;
141     return range
142         .retro
143         .indexOf(value, fromIndex)
144         .withFront!(a => range.walkLength - a - 1);
145 }
146 
147 ///
148 unittest {
149     import optional;
150     assert([1, 2, 1, 2].indexOf(2) == some(1));
151     assert([1, 2, 1, 2].indexOf(2, 2) == some(3));
152     assert([1, 2, 1, 2].indexOf(3) == none);
153 }