1 /** 2 Some helper functions for ranges 3 */ 4 module ggplotd.range; 5 6 version(unittest) { 7 import dunit.toolkit; 8 } 9 10 private struct HashSet(E) { 11 // TODO switch to proper implementation (not using AA) 12 bool put(E el) 13 { 14 if ( el in set ) 15 return false; 16 set[el] = set.length; 17 return true; 18 } 19 20 import std.range : ElementType; 21 22 bool put(R)(R range) 23 if (is(E==ElementType!R)) 24 { 25 auto result = false; 26 foreach(el; range) 27 { 28 auto r = this.put(el); 29 if (r) 30 result = true; 31 } 32 return result; 33 } 34 35 bool put(S)(in S hset) 36 if (is(S==HashSet!E)) 37 { 38 return this.put(hset.data); 39 } 40 41 @property auto data() const 42 { 43 import std.array : array; 44 import std.algorithm : map, sort; 45 auto kv = set.byKeyValue().array; 46 auto sorted = kv.sort!((a, b) => a.value < b.value); 47 return sorted.map!((a) => a.key); 48 } 49 50 auto @property length() const 51 { 52 return set.length; 53 } 54 55 size_t[E] set; 56 } 57 58 unittest 59 { 60 import std.array : array; 61 import std.range : walkLength; 62 HashSet!string set; 63 set.put("b"); 64 assertEqual(set.data.walkLength, 1); 65 set.put("b"); 66 assertEqual(set.data.walkLength, 1); 67 set.put(["a","b"]); 68 assertEqual(set.data.walkLength, 2); 69 assertEqual(set.data.array, ["b", "a"]); 70 HashSet!string set2; 71 set2.put(["a", "b", "c", "d", "e", "f", "g", "h", "i"]); 72 set.put(set2); 73 assertEqual(set.data.array, ["b", "a", "c", "d", "e", "f", "g", "h", "i"]); 74 } 75 76 /** 77 Lazily iterate over range and returning only uniques 78 */ 79 auto uniquer(R)(R range) 80 { 81 struct Unique(Range) 82 { 83 import std.range : ElementType; 84 85 Range range; 86 HashSet!(ElementType!Range) set; 87 88 this( Range _range ) 89 { 90 import std.range : front; 91 range = _range; 92 this.popFront; 93 } 94 95 @property auto front() 96 { 97 import std.range : front; 98 return range.front; 99 } 100 101 void popFront() 102 { 103 import std.range : front, popFront, empty; 104 while( !range.empty ) 105 { 106 // TODO: Currently this causes an unnecessary "initial" check, because we now that previous value was already added. I think the only way to solve this would be to keep currentValue and possible future value around. 107 if (set.put(range.front)) 108 { 109 currentFront = range.front; 110 break; 111 } 112 range.popFront; 113 } 114 } 115 116 @property bool empty() 117 { 118 import std.range : empty; 119 return range.empty; 120 } 121 122 ElementType!Range currentFront; 123 } 124 return Unique!R(range); 125 } 126 127 /// 128 unittest 129 { 130 import std.array : array; 131 assertEqual( [1,1,1,1].uniquer.array, [1] ); 132 assertEqual( [1].uniquer.array, [1] ); 133 assertEqual( "".uniquer.array, [] ); 134 assertEqual( [1,2,1,3].uniquer.array, [1,2,3] ); 135 assertEqual( [1,2,1,3,1,2].uniquer.array, [1,2,3] ); 136 assertEqual( [1,2,3].uniquer.array, [1,2,3] ); 137 assertEqual( ["a","b","a","c","a","c"].uniquer.array, ["a","b","c"] ); 138 139 import std.typecons : tuple; 140 assertEqual( [tuple(1,"a"),tuple(1,"b"),tuple(2,"b"),tuple(1,"b")] 141 .uniquer.array, [tuple(1,"a"),tuple(1,"b"),tuple(2,"b")] ); 142 } 143 144 /++ 145 Group an (unsorted) range by the result of the function applied to each element 146 +/ 147 private import std.range : isInputRange; 148 149 auto groupBy(alias func = function(a) { return a; }, R)(R values) 150 if (isInputRange!R) 151 { 152 import std.range : front, ElementType; 153 alias K = typeof(func(values.front)); 154 alias V = ElementType!R[]; 155 V[K] grouped; 156 foreach(value; values) 157 grouped[func(value)] ~= value; 158 return grouped; 159 } 160 161 /// 162 unittest { 163 import std.stdio : writeln; 164 import std.algorithm : sort; 165 import std.typecons : tuple; 166 auto xs = [ 167 tuple("a", 1.0), 168 tuple("b", 3.0), 169 tuple("a", 2.0), 170 tuple("b", 4.0)]; 171 auto grouped = xs.groupBy!((a) => a[0]); 172 assertEqual( grouped.keys.length, 2 ); 173 assertEqual( grouped.keys.sort(), ["a","b"].sort() ); 174 } 175 176 /++ 177 Merge the elements of two ranges. If first is not a range then merge that with each element of the second range and vice versa. 178 +/ 179 auto mergeRange( R1, R2 )( R1 r1, R2 r2 ) 180 if (isInputRange!R1 || isInputRange!R2) 181 { 182 import std.range : zip, StoppingPolicy, walkLength, repeat; 183 import std.algorithm : map; 184 185 import ggplotd.aes : merge; 186 static if (isInputRange!R1 && isInputRange!R2) 187 return zip(StoppingPolicy.longest, r1,r2).map!((a) => a[0].merge( a[1] ) ); 188 else 189 { 190 // TODO: should not have to repeat r2 for more than once with stoppingpolicy.longest, 191 // but currently doing that crashes. Probably compiler bug, might try changing it later 192 static if (isInputRange!R1) 193 return zip(StoppingPolicy.longest, r1, r2.repeat(r1.walkLength)) 194 .map!((a) => a[0].merge( a[1] ) ); 195 else 196 return zip(StoppingPolicy.longest, r1.repeat(r2.walkLength), r2 ) 197 .map!((a) => a[0].merge( a[1] ) ); 198 } 199 } 200 201 unittest 202 { 203 import std.range : walkLength; 204 import ggplotd.aes : Aes, DefaultValues; 205 auto m = [DefaultValues].mergeRange( Aes!(double[], "x")([1.0])); 206 assertEqual( m.walkLength, 1); 207 } 208 209 210 /// 211 unittest 212 { 213 import std.range : front; 214 import ggplotd.aes : Aes, DefaultValues; 215 216 auto xs = ["a", "b"]; 217 auto ys = ["c", "d"]; 218 auto labels = ["e", "f"]; 219 auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels); 220 auto nlAes = mergeRange(DefaultValues, aes ); 221 assertEqual(nlAes.front.x, "a"); 222 assertEqual(nlAes.front.label, "e"); 223 assertEqual(nlAes.front.colour, "black"); 224 auto nlAes2 = aes.mergeRange(DefaultValues); 225 assertEqual(nlAes2.front.x, "a"); 226 assertEqual(nlAes2.front.label, ""); 227 assertEqual(nlAes2.front.colour, "black"); 228 }