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 }