1 /** 2 Some additional range functions. 3 4 Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de> 5 License: Subject to the terms of the MIT license, as written in the 6 included LICENSE file. 7 Authors: Arne Ludwig <arne.ludwig@posteo.de> 8 */ 9 module dalicious.range; 10 11 import std.functional : unaryFun; 12 import std.meta : AliasSeq, staticMap; 13 import std.range : ElementType, isInputRange; 14 import std.traits : rvalueOf; 15 import std.typecons : tuple, Tuple; 16 17 /** 18 This range iterates over fixed-sized chunks of size chunkSize of a source 19 range. Source must be an input range. chunkSize must be greater than zero. 20 21 See Also: `std.range.chunks` 22 Returns: Range of chunks, ie. `ElementType!Source[]`. 23 */ 24 auto arrayChunks(Source)(Source range, in size_t chunkSize) if (isInputRange!Source) 25 { 26 alias Element = ElementType!Source; 27 28 static struct ArrayChunks 29 { 30 private Source range; 31 private const size_t chunkSize; 32 private Element[] chunk; 33 34 this(Source range, in size_t chunkSize) 35 { 36 this.range = range; 37 this.chunkSize = chunkSize; 38 this.popFront(); 39 } 40 41 void popFront() 42 { 43 if (range.empty) 44 { 45 chunk = null; 46 47 return; 48 } 49 50 chunk = new Element[chunkSize]; 51 52 foreach (i; 0 .. chunkSize) 53 { 54 chunk[i] = range.front; 55 56 if (range.empty) 57 { 58 chunk = chunk[0 .. i + 1]; 59 break; 60 } 61 else 62 { 63 range.popFront(); 64 } 65 } 66 } 67 68 @property Element[] front() 69 { 70 return chunk; 71 } 72 73 @property bool empty() 74 { 75 return chunk is null; 76 } 77 } 78 79 assert(chunkSize > 0, "chunkSize must be greater than zero"); 80 81 return ArrayChunks(range, chunkSize); 82 } 83 84 /// 85 unittest 86 { 87 import std.array : array; 88 import std.range : iota; 89 90 auto chunks = iota(10).arrayChunks(2); 91 assert(chunks.array == [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]); 92 } 93 94 /// Generate a tuple of tuples of chunkSize. 95 template chunks(size_t chunkSize) 96 { 97 auto chunks(T...)(T args) pure nothrow @safe if (args.length >= chunkSize) 98 { 99 return tuple(tuple(args[0 .. chunkSize]), chunks(args[chunkSize .. $]).expand); 100 } 101 102 auto chunks(T...)(T args) pure nothrow @safe 103 if (0 < args.length && args.length < chunkSize) 104 { 105 return tuple(tuple(args[0 .. $])); 106 } 107 108 auto chunks(T...)(T args) pure nothrow @safe if (args.length == 0) 109 { 110 return tuple(); 111 } 112 } 113 114 /// 115 unittest 116 { 117 auto c1 = chunks!2(0, 1, 2, 3, 4, 5); 118 119 assert(c1 == tuple(tuple(0, 1), tuple(2, 3), tuple(4, 5))); 120 121 auto c2 = chunks!3(false, "1", 2.0, 3, '4', 5); 122 123 assert(c2 == tuple(tuple(false, "1", 2.0), tuple(3, '4', 5))); 124 125 enum c4 = chunks!4(false, "1", 2.0, 3, '4', 5); 126 127 static assert(c4 == tuple(tuple(false, "1", 2.0, 3), tuple('4', 5))); 128 } 129 130 /// Split a list of aliases into chunks. 131 template Chunks(size_t chunkSize, T...) 132 { 133 static if (T.length >= chunkSize) 134 { 135 alias Chunks = AliasSeq!(Chunk!(T[0 .. chunkSize]), Chunks!(chunkSize, T[chunkSize .. $])); 136 } 137 else static if (0 < T.length && T.length < chunkSize) 138 { 139 alias Chunks = AliasSeq!(Chunk!(T[0 .. $])); 140 } 141 else static if (T.length == 0) 142 { 143 alias Chunks = AliasSeq!(); 144 } 145 else 146 { 147 static assert(0); 148 } 149 } 150 151 template Chunk(T...) 152 { 153 struct Chunk 154 { 155 alias chunks = T; 156 } 157 } 158 159 /// 160 unittest 161 { 162 alias c1 = Chunks!(2, AliasSeq!(int, int, int, int, int, int)); 163 164 static assert(is(c1 == AliasSeq!(Chunk!(int, int), Chunk!(int, int), Chunk!(int, int)))); 165 static foreach (pair; c1) 166 { 167 static foreach (type; pair.chunks) 168 { 169 static assert(is(type == int)); 170 } 171 } 172 } 173 174 /* 175 Build a comparator according to `pred`. 176 */ 177 template Comparator(pred...) if (pred.length == 1) 178 { 179 /// Return comparison value akin to `opCmp`. 180 int compare(S, T = S)(in S a, in T b) 181 { 182 alias _pred = unaryFun!pred; 183 auto _a = _pred(a); 184 auto _b = _pred(b); 185 186 if (_a < _b) 187 return -1; 188 else if (_a == _b) 189 return 0; 190 else 191 return 1; 192 } 193 194 /// Return `true` iff `a < b`. 195 bool lt(S, T = S)(in S a, in T b) 196 { 197 return compare!(S, T)(a, b) < 0; 198 } 199 200 /// Return `true` iff `a <= b`. 201 bool le(S, T = S)(in S a, in T b) 202 { 203 return compare!(S, T)(a, b) <= 0; 204 } 205 206 /// Return `true` iff `a == b`. 207 bool eq(S, T = S)(in S a, in T b) 208 { 209 return compare!(S, T)(a, b) == 0; 210 } 211 212 /// Return `true` iff `a >= b`. 213 bool ge(S, T = S)(in S a, in T b) 214 { 215 return compare!(S, T)(a, b) >= 0; 216 } 217 218 /// Return `true` iff `a > b`. 219 bool gt(S, T = S)(in S a, in T b) 220 { 221 return compare!(S, T)(a, b) > 0; 222 } 223 } 224 225 /// 226 unittest 227 { 228 alias compareSquares = Comparator!"a ^^ 2".compare; 229 230 assert(compareSquares(1, 2) < 0); 231 assert(compareSquares(1, -2) < 0); 232 assert(compareSquares(-1, 1) == 0); 233 assert(compareSquares(-2.0, 1) > 0); 234 235 alias compareByLength = Comparator!"a.length".compare; 236 237 assert(compareByLength([], [1]) < 0); 238 assert(compareByLength([1, 2], [1]) > 0); 239 assert(compareByLength([1, 2], ["1", "2"]) == 0); 240 241 alias compareAbsInts = Comparator!("a > 0 ? a : -a").compare!(int); 242 243 assert(compareSquares(1, 2) < 0); 244 assert(compareSquares(1, -2) < 0); 245 assert(compareSquares(-1, 1) == 0); 246 assert(compareSquares(-2, 1) > 0); 247 static assert(!__traits(compiles, compareAbsInts(-2.0, 1.0))); 248 249 alias ltSquared = Comparator!("a ^^ 2").lt; 250 251 assert(ltSquared(1, 2)); 252 assert(ltSquared(1, -2)); 253 assert(!ltSquared(-2, -1)); 254 255 alias eqSquared = Comparator!("a ^^ 2").eq; 256 257 assert(eqSquared(1, 1)); 258 assert(eqSquared(1, -1)); 259 assert(!eqSquared(1, 2)); 260 } 261 262 /// Take exactly `n` element from range. Throws an exception if range has not 263 /// enough elements. 264 /// 265 /// Throws: Exception if range has less than `n` elements. 266 ElementType!R[n] takeExactly(size_t n, R)(R range) if (isInputRange!R) 267 { 268 import std.exception : enforce; 269 270 ElementType!R[n] result; 271 size_t i = 0; 272 273 while (!range.empty && i < n) 274 { 275 result[i++] = range.front; 276 range.popFront(); 277 } 278 279 enforce!Exception(i == n, "not enough elements"); 280 281 return result; 282 } 283 284 /// 285 unittest 286 { 287 import std.exception : assertThrown; 288 import std.range : iota; 289 290 static assert(is(typeof(iota(10).takeExactly!5) == int[5])); 291 assert(iota(10).takeExactly!5 == [0, 1, 2, 3, 4]); 292 293 assertThrown!Exception(iota(2).takeExactly!5); 294 } 295 296 class WrapLinesImpl(R) 297 { 298 R output; 299 size_t lineWidth; 300 size_t column; 301 302 this(R output, size_t lineWidth) 303 { 304 this.output = output; 305 this.lineWidth = lineWidth; 306 } 307 308 void put(inout(char) c) 309 { 310 import std.range.primitives; 311 312 assert(c == '\n' || column < lineWidth); 313 314 std.range.primitives.put(output, c); 315 ++column; 316 317 if (c == '\n') 318 { 319 column = 0; 320 } 321 322 if (column >= lineWidth) 323 { 324 put('\n'); 325 } 326 } 327 328 void put(string chunk) 329 { 330 foreach (c; chunk) 331 { 332 put(c); 333 } 334 } 335 } 336 337 auto wrapLines(R)(R output, size_t lineWidth) 338 { 339 return new WrapLinesImpl!R(output, lineWidth); 340 } 341 342 unittest 343 { 344 import std.range.primitives : put; 345 346 auto outputBuffer = new dchar[12]; 347 auto output = wrapLines(outputBuffer, 10); 348 349 put(output, "hello world"); 350 351 assert(outputBuffer == "hello worl\nd"); 352 } 353 354 /// Return a tuple of `fun` applied to each value of `tuple`. 355 auto tupleMap(alias fun, Types...)(in Types values) 356 { 357 alias mapper = unaryFun!fun; 358 alias MappedValue(V) = typeof(mapper(rvalueOf!V)); 359 alias MappedTuple = Tuple!(staticMap!(MappedValue, Types)); 360 361 MappedTuple mappedTuple; 362 363 static foreach (i; 0 .. Types.length) 364 { 365 mappedTuple[i] = mapper(values[i]); 366 } 367 368 return mappedTuple; 369 } 370 371 /// 372 unittest 373 { 374 import std.conv : to; 375 import std.typecons : tuple; 376 377 assert( 378 tupleMap!"2*a"(1, 2, 3.0) == 379 tuple(2, 4, 6.0) 380 ); 381 assert( 382 tupleMap!(x => to!string(x))(1, '2', 3.0) == 383 tuple("1", "2", "3") 384 ); 385 }