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