1 /** 2 Removes elements from a range 3 */ 4 module ddash.algorithm.pull; 5 6 /// 7 unittest { 8 int[] arr = [1, 2, 3, 4, 5]; 9 arr.pull(1, [2, 5]); 10 assert(arr == [3, 4]); 11 12 import std.math: ceil; 13 double[] farr = [2.1, 1.2]; 14 assert(farr.pull!ceil([2.3, 3.4]) == [1.2]); 15 16 farr = [2.1, 1.2]; 17 assert(farr.pull!((a, b) => ceil(a) == ceil(b))([2.3, 3.4]) == [1.2]); 18 19 arr = [1, 2, 3, 4, 5]; 20 assert(arr.pull!"a == b - 1"(4, 6) == [1, 2, 4]); 21 } 22 23 /// 24 unittest { 25 struct A { 26 int x; 27 int y; 28 } 29 30 auto arr = [A(1, 2), A(3, 4), A(5, 6)]; 31 assert(arr.pullBy!"y"(2, 6) == [A(3, 4)]); 32 } 33 34 import ddash.common; 35 36 /** 37 Removes elements from a range. 38 39 Params: 40 range = a mutable range 41 values = variables args of ranges and values to pull out 42 pred = a unary transoform predicate or binary equality predicate. Defaults to `==`. 43 44 Returns: 45 Modified range 46 47 Since: 48 0.0.1 49 */ 50 ref pull(alias pred = null, Range, Values...)(return ref Range range, Values values) 51 if (from!"std.range".isInputRange!Range) 52 { 53 return range.pullBase!("", pred)(values); 54 } 55 56 /** 57 Removes elements from a range by a publicly visible field of `ElemntType!Range` 58 59 Params: 60 range = a mutable range 61 values = variables args of ranges and values to pull out 62 member = which member in `ElementType!Range` to pull by 63 pred = a unary transform predicate or binary equality predicate. Defaults to `==`. 64 65 Returns: 66 Modified range 67 68 Since: 69 0.0.1 70 */ 71 ref pullBy(string member, alias pred = null, Range, Values...)(return ref Range range, Values values) 72 if (from!"std.range".isInputRange!Range) 73 { 74 return range.pullBase!(member, pred)(values); 75 } 76 77 private ref pullBase(string member, alias pred, Range, Values...)(return ref Range range, Values values) { 78 import std.algorithm: canFind, remove; 79 import ddash.algorithm: concat, equal; 80 import ddash.common.valueby; 81 // elements from values will be lhs 82 alias reverseEqual = (a, b) => equal!pred(b, a); 83 auto unwanted = concat(values); 84 static if (member == "") 85 { 86 alias transform = (a) => a; 87 } 88 else 89 { 90 alias transform = (a) => a.valueBy!member; 91 } 92 range = range 93 .remove!(a => unwanted 94 .canFind!reverseEqual(transform(a)) 95 ); 96 return range; 97 } 98 99 /** 100 Returns a range excluding the specified indices. 101 102 The indices can be given as a range of indices, or single integral arguments or a mixture of both. 103 All index arguments will be $(DDOX_NAMED_REF algorithm.concat, `concat`)ed together and sorted 104 before returning a range that then goes through the original range exlusing the resulting concatenation 105 of indices. 106 107 The running time is $(B O(n)) if `indices` is a single sorted range or a single index to remove, else 108 there's an additional cost of standard sort running time. 109 110 Params: 111 range = an input range 112 indices = zero or more integral elements or ranges 113 114 Returns: 115 A range excluding the supplied indices 116 117 Benchmarks: 118 $(LI single args: `pullAt(8, 16, 14...)`) 119 $(LI single range: `pullAt([8, 16, 14...])`) 120 $(LI sorted range: `pullAt([8, 16, 14...].sort)`) 121 $(LI canFind range: `indices.filter!(canFind)`) 122 $(LI canFind sorted: `indices.sort.filter!(canFind)`) 123 --- 124 Benchmarking pullAt against filter/canFind: 125 numbers: [12, 11, 1, 9, 11, 4, 1, 4, 2, 7, 16, 8, 8, 9, 6, 15, 9, 0, 15, 2] 126 indices: [8, 16, 14, 11, 0, 16, 12, 10, 15, 17] 127 pullAt: 128 single args: 3 ms, 885 μs, and 6 hnsecs 129 single range: 1 ms and 610 μs 130 sorted range: 185 μs and 2 hnsecs 131 canFind range: 5 ms and 547 μs 132 canFind sorted: 4 hnsecs 133 pullAt (with .array): 134 single args: 8 ms, 765 μs, and 8 hnsecs 135 single range: 6 ms, 823 μs, and 8 hnsecs 136 sorted range: 6 ms, 571 μs, and 2 hnsecs 137 canFind range: 10 ms, 479 μs, and 2 hnsecs 138 canFind sorted: 9 ms, 330 μs, and 5 hnsecs 139 --- 140 141 Since: 142 0.0.1 143 */ 144 auto pullAt(Range, Indices...)(Range range, Indices indices) 145 if (from!"std.range".isInputRange!Range 146 && from!"std.meta".allSatisfy!( 147 from!"std.traits".isIntegral, 148 from!"bolts.meta".Flatten!Indices 149 ) 150 ) { 151 import std.algorithm: sort; 152 import std.range: empty, popFront, front, isInputRange; 153 import std.array; 154 import ddash.algorithm: concat; 155 import bolts.range: isSortedRange; 156 157 // If we only have one element or we are a sorted range then there's no need to sort 158 static if (Indices.length == 1 && (!isInputRange!(Indices[0]) || isSortedRange!(Indices[0]))) 159 { 160 auto normalizedIndices = concat(indices); 161 } 162 else 163 { 164 auto normalizedIndices = concat(indices).array.sort; 165 } 166 167 alias I = typeof(normalizedIndices); 168 static struct Result { 169 Range source; 170 I indices; 171 size_t currentIndex; 172 this(Range r, I i) { 173 this.source = r; 174 this.indices = i; 175 this.moveToNextElement; 176 } 177 void moveToNextElement() { 178 while (!this.source.empty && !this.indices.empty && this.indices.front == this.currentIndex) { 179 this.currentIndex++; 180 this.source.popFront; 181 this.indices.popFront; 182 } 183 } 184 auto ref front() @property { 185 return this.source.front; 186 } 187 bool empty() @property { 188 return this.source.empty; 189 } 190 void popFront() { 191 this.currentIndex++; 192 this.source.popFront; 193 194 this.moveToNextElement; 195 } 196 } 197 return Result(range, normalizedIndices); 198 } 199 200 unittest { 201 assert([1, 2, 3, 4].pullAt(1, 2, 3).equal([1])); 202 assert([1, 2, 3, 4].pullAt(0, 3).equal([2, 3])); 203 assert([1, 2, 3, 4].pullAt(0, 5).equal([2, 3, 4])); 204 assert([1, 2, 3, 4].pullAt([2, 1]).equal([1, 4])); 205 assert([1, 2, 3, 4].pullAt([2, 1, 0, 3]).empty); 206 }