1 /**
2     Creates a range of unique values that are included in all given ranges
3 */
4 module ddash.algorithm.intersection;
5 
6 ///
7 unittest {
8     assert([1, 2, 3].intersection([1], 3).equal([1, 3]));
9 
10     import std.math: ceil;
11     assert([2.1, 1.2].intersection!ceil([2.3, 3.4]).equal([2.1]));
12     assert([2.1, 1.2].intersection!((a, b) => ceil(a) == ceil(b))([2.3, 3.4]).equal([2.1]));
13 
14     struct A {
15         int value;
16     }
17     assert([A(1), A(2), A(3)].intersection!((a, b) => a.value == b.value)([A(2), A(3)]).equal([A(2), A(3)]));
18 }
19 
20 import ddash.common;
21 
22 struct Intersection(alias pred, R1, R2) if (from!"std.range".isInputRange!R1 && from!"std.range".isInputRange!R2) {
23     import std.range: ElementType;
24 
25     R1 r1;
26     R2 r2;
27 
28     alias E = ElementType!R1;
29 
30     private void moveToNextElement() {
31         import bolts.traits: isBinaryOver, isNullType;
32         import bolts.range: isSortedRange;
33         import std.range: empty, front, popFront;
34         static if (!isBinaryOver!(pred, E) && isSortedRange!R1 && isSortedRange!R2)
35         {
36             import bolts.range: sortingPredicate;
37             static if (isNullType!pred)
38             {
39                 alias comp = (a, b) => sortingPredicate!R1(a, b);
40             }
41             else
42             {
43                 alias comp = (a, b) => sortingPredicate!R1(pred(a), pred(b));
44             }
45 
46             while (!this.r1.empty && !this.r2.empty) {
47                 if (comp(this.r1.front, this.r2.front)) {
48                     this.r1.popFront;
49                 } else if (comp(this.r2.front, this.r1.front)) {
50                     this.r2.popFront;
51                 } else {
52                     break;
53                 }
54             }
55         }
56         else
57         {
58             import std.algorithm: canFind;
59             import ddash.algorithm: equal;
60             alias eq = (a, b) => equal!pred(a, b);
61             while (!this.r1.empty && !this.r2.canFind!eq(this.r1.front)) {
62                 this.r1.popFront;
63             }
64         }
65     }
66 
67     this(R1 r1, R2 r2) {
68         this.r1 = r1;
69         this.r2 = r2;
70         this.moveToNextElement;
71     }
72 
73     bool empty() @property {
74         import std.range: empty;
75         return this.r1.empty || this.r2.empty;
76     }
77     auto front() @property {
78         import std.range: front;
79         return this.r1.front;
80     }
81     void popFront() {
82         import std.range: popFront;
83         this.r1.popFront;
84         this.moveToNextElement;
85     }
86 }
87 
88 /**
89     Creates a range of unique values that are included in all given ranges
90 
91     The `pred` defaults to null. If a unary predicate is passed in, then a transformation
92     will be appled to each element before comparing. If a binary predicate is passed in, it
93     will determine equality of elements.
94 
95     If `pred` is null or unary, and the range is sortable or is sorted, an optimized linear
96     algorithm will be used instead using the range's
97     $(DDOX_NAMED_REF range.sortingpredicate, `sorting predicate`).
98 
99     Params:
100         pred = unary transformation or binary comparator
101         range = the range to inspect
102         values = ranges or single values to exclude
103 
104     Returns:
105         New array of filtered results. If `Rs` is empty, then empty `range` is returned
106 
107     Since:
108         0.0.1
109 */
110 auto intersection(alias pred = null, Range, Rs...)(Range range, Rs values)
111 if (from!"std.range".isInputRange!Range
112     && (from!"bolts.traits".isNullType!pred
113         || from!"bolts.traits".isUnaryOver!(pred, from!"std.range".ElementType!Range)
114         || from!"bolts.traits".isBinaryOver!(pred, from!"std.range".ElementType!Range)))
115 {
116     static if (!Rs.length)
117     {
118         import std.range: takeNone;
119         return range.takeNone;
120     }
121     else
122     {
123         import std.range: ElementType;
124         import ddash.algorithm: concat;
125         import bolts.traits: isNullType, isUnaryOver;
126 
127         auto combinedValues = values.concat;
128         static assert (is(ElementType!(typeof(combinedValues)) : ElementType!Range));
129 
130         static if (isNullType!pred || isUnaryOver!(pred, ElementType!Range))
131         {
132             import std.algorithm: sort;
133             static if (is(typeof(range.sort)))
134             {
135                 auto r1 = range.sort;
136             }
137             else
138             {
139                 auto r1 = range;
140             }
141 
142             static if (is(typeof(combinedValues.sort)))
143             {
144                 auto r2 = combinedValues.sort;
145             }
146             else
147             {
148                 auto r2 = combinedValues;
149             }
150         }
151         else
152         {
153             auto r1 = range;
154             auto r2 = combinedValues;
155         }
156 
157         return Intersection!(pred, typeof(r1), typeof(r2))(r1, r2);
158     }
159 }
160 
161 unittest {
162     assert([1, 2, 3].intersection([0, 1, 2]).equal([1, 2]));
163     assert([1, 2, 3].intersection([1, 2]).equal([1, 2]));
164     assert([1, 2, 3].intersection([1], 2).equal([1, 2]));
165     assert([1, 2, 3].intersection([1], [3]).equal([1, 3]));
166     assert([1, 2, 3].intersection(3).equal([3]));
167 }
168 
169 unittest {
170     // Implicitly convertible elements ok
171     assert([1.0, 2.0].intersection(2).equal([2.0]));
172 
173     // Implicitly convertible ranges ok
174     assert([1.0, 2.0].intersection([2]).equal([2.0]));
175 
176     // Non implicily convertible elements not ok
177     static assert(!__traits(compiles, [1].intersection(1.0)));
178 
179     // Non implicily convertible range not ok
180     static assert(!__traits(compiles, [1].intersection([1.0])));
181 }
182 
183 unittest {
184     import std.math: ceil;
185     assert([2.1, 1.2].intersection!ceil([2.3, 3.4]).equal([2.1]));
186     assert([2.1, 1.2].intersection!((a, b) => ceil(a) == ceil(b))([2.3, 3.4]).equal([2.1]));
187 }
188 
189 unittest {
190     struct A {
191         int value;
192     }
193     assert([A(1), A(2), A(3)].intersection!((a, b) => a.value == b.value)([A(2), A(3)]).equal([A(2), A(3)]));
194 }