1 module nxt.unique_range; 2 3 import std.range.primitives : hasLength; 4 5 /** Unique range (slice) owning its source of `Source`. 6 7 Copy construction is disabled, explicit copying is instead done through 8 member `.dup`. 9 */ 10 struct UniqueRange(Source) 11 if (hasLength!Source) /+ TODO: use traits `isArrayContainer` checking fo +/ 12 { 13 pure nothrow @safe @nogc: 14 import std.range.primitives : ElementType, isBidirectionalRange; 15 import std.traits : isArray; 16 alias SourceRange = typeof(Source.init[]); 17 alias E = ElementType!SourceRange; 18 19 this(this) @disable; // not intended to be copied 20 21 /// Construct from `source`. 22 this(Source source) 23 { 24 import std.algorithm.mutation : move; 25 () @trusted { move(source, _source); } (); /+ TODO: remove `move` when compiler does it for us +/ 26 _sourceRange = _source[]; 27 } 28 29 /// Construct from reference to `source`, used by `intoUniqueRange`. 30 private this(ref Source source) 31 { 32 import std.algorithm.mutation : move; 33 () @trusted { move(source, _source); } (); /+ TODO: remove `move` when compiler does it for us +/ 34 _sourceRange = _source[]; 35 } 36 37 /// Is `true` if range is empty. 38 bool empty() const @property @trusted 39 { 40 static if (!__traits(hasMember, SourceRange, "empty")) 41 import std.range.primitives : empty; 42 return (cast(Unqual!SourceRange)_sourceRange).empty; /+ TODO: remove cast and @trusted when SortedRange.empty is const +/ 43 } 44 45 /// Returns: length of `this`. 46 static if (hasLength!(typeof(Source.init[]))) 47 size_t length() const @property @trusted => (cast(Unqual!SourceRange)_sourceRange).length; /+ TODO: remove cast and @trusted when SortedRange.empty is const +/ 48 49 /// Front element. 50 @property scope auto ref inout(E) front() inout return @trusted in(!empty) 51 { 52 static if (!__traits(hasMember, SourceRange, "front")) 53 import std.range.primitives : front; 54 return cast(inout(E))(cast(SourceRange)_sourceRange).front; 55 } 56 57 /// Pop front element. 58 void popFront() in(!empty) 59 { 60 static if (!__traits(hasMember, SourceRange, "popFront")) 61 import std.range.primitives : popFront; 62 _sourceRange.popFront(); // should include check for emptyness 63 } 64 65 /// Pop front element and return it. 66 E takeFront()() in(!empty) 67 { 68 scope(exit) popFront(); 69 static if (__traits(isPOD, E)) 70 return front; 71 else 72 { 73 import std.algorithm.mutation : move; 74 return move(front); 75 } 76 } 77 78 static if (isBidirectionalRange!(typeof(Source.init[]))) 79 { 80 /// Back element. 81 @property scope auto ref inout(E) back() inout return @trusted in(!empty) 82 { 83 static if (!__traits(hasMember, SourceRange, "back")) 84 import std.range.primitives : back; 85 return cast(inout(E))(cast(SourceRange)_sourceRange).back; 86 } 87 88 /// Pop back element. 89 void popBack() in(!empty) 90 { 91 static if (!__traits(hasMember, SourceRange, "popBack")) 92 import std.range.primitives : popBack; 93 _sourceRange.popBack(); // should include check for emptyness 94 } 95 96 /// Pop back element and return it. 97 E takeBack()() in(!empty) 98 { 99 import core.internal.traits : hasElaborateDestructor; 100 static if (__traits(isCopyable, E) && 101 !hasElaborateDestructor!E) 102 { 103 typeof(return) value = back; 104 popBack(); 105 return value; 106 } 107 else 108 { 109 static assert(0, "TODO: if back is an l-value move it out and return it"); 110 // import std.algorithm.mutation : move; 111 // import core.internal.traits : Unqual; 112 /+ TODO: reinterpret as typeof(*(cast(Unqual!E*)(&_source[_backIx]))) iff `E` doesn't contain any immutable indirections +/ 113 // typeof(return) value = move(_sourceRange.back); 114 // popBack(); 115 // return value; 116 } 117 } 118 alias stealBack = takeBack; 119 } 120 121 /// Returns: shallow duplicate of `this`. 122 static if (__traits(hasMember, Source, "dup")) 123 @property typeof(this) dup()() const => typeof(return)(_source.dup); 124 125 private: 126 Source _source; // typically a non-reference counted container type with disable copy construction 127 SourceRange _sourceRange; 128 } 129 130 /** Returns: A range of `Source` that owns its `source` (data container). 131 Similar to Rust's `into_iter`. 132 */ 133 UniqueRange!Source intoUniqueRange(Source)(Source source) 134 => typeof(return)(source); // construct from reference 135 136 /// A generator is a range which owns its state (typically a non-reference counted container). 137 alias intoGenerator = intoUniqueRange; 138 139 /// basics 140 pure nothrow @safe @nogc unittest { 141 import std.experimental.allocator.mallocator : Mallocator; 142 import nxt.container.dynamic_array : DA = DynamicArray; 143 import std.traits : isIterable; 144 import std.range.primitives : isInputRange; 145 alias C = DA!(int, Mallocator); 146 147 auto cs = C([11, 13, 15, 17].s).intoUniqueRange; 148 auto cs2 = C([11, 13, 15, 17].s).intoUniqueRange; 149 // TODO: instead use auto cs2 = cs.dupShallow; 150 151 assert(cs !is cs2); 152 153 assert(cs == cs2); 154 cs2.popFront(); 155 assert(cs2.length == 3); 156 assert(cs != cs2); 157 158 static assert(isInputRange!(typeof(cs))); 159 static assert(isIterable!(typeof(cs))); 160 161 assert(!cs.empty); 162 assert(cs.length == 4); 163 assert(cs.front == 11); 164 assert(cs.back == 17); 165 166 cs.popFront(); 167 assert(cs.length == 3); 168 assert(cs.front == 13); 169 assert(cs.back == 17); 170 171 cs.popBack(); 172 assert(cs.length == 2); 173 assert(cs.front == 13); 174 assert(cs.back == 15); 175 176 assert(cs.takeFront() == 13); 177 assert(cs.length == 1); 178 assert(cs.front == 15); 179 assert(cs.back == 15); 180 181 assert(cs.takeBack() == 15); 182 assert(cs.length == 0); 183 assert(cs.empty); 184 } 185 186 /// combined with Phobos ranges 187 pure nothrow @safe unittest { 188 import std.experimental.allocator.mallocator : Mallocator; 189 import nxt.container.dynamic_array : DA = DynamicArray; 190 alias C = DA!(int, Mallocator); 191 assert(C([11, 13, 15, 17].s) 192 .intoUniqueRange() 193 .filterUnique!(_ => _ != 11) 194 .mapUnique!(_ => 2*_) 195 .equal([2*13, 2*15, 2*17])); 196 } 197 198 import std.functional : unaryFun; 199 200 template mapUnique(fun...) 201 if (fun.length >= 1) 202 { 203 import std.algorithm.mutation : move; 204 import std.range.primitives : isInputRange, ElementType; 205 import core.internal.traits : Unqual; 206 207 auto mapUnique(Range)(Range r) 208 if (isInputRange!(Unqual!Range)) 209 { 210 import std.meta : AliasSeq, staticMap; 211 212 alias RE = ElementType!(Range); 213 static if (fun.length > 1) 214 { 215 import std.functional : adjoin; 216 import std.meta : staticIndexOf; 217 218 alias _funs = staticMap!(unaryFun, fun); 219 alias _fun = adjoin!_funs; 220 221 // Once DMD issue #5710 is fixed, this validation loop can be moved into a template. 222 foreach (f; _funs) 223 static assert(!is(typeof(f(RE.init)) == void), 224 "Mapping function(s) must not return void: " ~ _funs.stringof); 225 } 226 else 227 { 228 alias _fun = unaryFun!fun; 229 alias _funs = AliasSeq!(_fun); 230 231 // Do the validation separately for single parameters due to DMD issue #15777. 232 static assert(!is(typeof(_fun(RE.init)) == void), 233 "Mapping function(s) must not return void: " ~ _funs.stringof); 234 } 235 236 return MapUniqueResult!(_fun, Range)(move(r)); 237 } 238 } 239 240 private struct MapUniqueResult(alias fun, Range) 241 { 242 import core.internal.traits : Unqual; 243 import std.range.primitives : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange, isInfinite, hasSlicing; 244 import std.algorithm.mutation : move; 245 246 alias R = Unqual!Range; 247 R _input; 248 249 this(R input) 250 { 251 _input = move(input); /+ TODO: remove `move` when compiler does it for us +/ 252 } 253 254 static if (isInfinite!R) 255 enum bool empty = false; ///< Propagate infinite-ness. 256 else 257 @property bool empty() => _input.empty; 258 259 auto ref front() @property in(!empty) => fun(_input.front); 260 void popFront() in(!empty) => _input.popFront(); 261 262 static if (isBidirectionalRange!R) 263 { 264 auto ref back()() @property in(!empty) => fun(_input.back); 265 void popBack()() in(!empty) => _input.popBack(); 266 } 267 268 static if (isRandomAccessRange!R) 269 { 270 static if (is(typeof(_input[ulong.max]))) 271 private alias opIndex_t = ulong; 272 else 273 private alias opIndex_t = uint; 274 auto ref opIndex(opIndex_t index) => fun(_input[index]); 275 } 276 277 static if (hasLength!R) 278 { 279 @property auto length() => _input.length; 280 alias opDollar = length; 281 } 282 283 static if (hasSlicing!R && 284 __traits(isCopyable, R)) 285 { 286 static if (is(typeof(_input[ulong.max .. ulong.max]))) 287 private alias opSlice_t = ulong; 288 else 289 private alias opSlice_t = uint; 290 static if (hasLength!R) 291 auto opSlice(opSlice_t low, opSlice_t high) => typeof(this)(_input[low .. high]); 292 else static if (is(typeof(_input[opSlice_t.max .. $]))) 293 { 294 struct DollarToken{} 295 enum opDollar = DollarToken.init; 296 auto opSlice(opSlice_t low, DollarToken) => typeof(this)(_input[low .. $]); 297 import std.range : takeExactly; 298 auto opSlice(opSlice_t low, opSlice_t high) => this[low .. $].takeExactly(high - low); 299 } 300 } 301 302 static if (isForwardRange!R && 303 __traits(isCopyable, R)) /+ TODO: should save be allowed for non-copyable? +/ 304 @property auto save() => typeof(this)(_input.save); 305 } 306 307 /+ TODO: Add duck-typed interface that shows that result is still sorted according to `predicate` +/ 308 template filterUnique(alias predicate) 309 if (is(typeof(unaryFun!predicate))) 310 { 311 import std.algorithm.mutation : move; 312 import std.range.primitives : isInputRange; 313 import core.internal.traits : Unqual; 314 auto filterUnique(Range)(Range range) 315 if (isInputRange!(Unqual!Range)) 316 => FilterUniqueResult!(unaryFun!predicate, Range)(move(range)); 317 } 318 319 /+ TODO: Add duck-typed interface that shows that result is still sorted according to `predicate` +/ 320 private struct FilterUniqueResult(alias pred, Range) 321 { 322 import std.algorithm.mutation : move; 323 import std.range.primitives : isForwardRange, isInfinite; 324 import core.internal.traits : Unqual; 325 alias R = Unqual!Range; 326 R _input; 327 328 this(R r) 329 { 330 _input = move(r); /+ TODO: remove `move` when compiler does it for us +/ 331 while (!_input.empty && !pred(_input.front)) 332 _input.popFront(); 333 } 334 335 static if (__traits(isCopyable, Range)) 336 auto opSlice() => this; 337 338 static if (isInfinite!Range) 339 enum bool empty = false; 340 else 341 bool empty() @property => _input.empty; 342 343 void popFront() 344 { 345 do 346 _input.popFront(); 347 while (!_input.empty && !pred(_input.front)); 348 } 349 350 auto ref front() @property in(!empty) => _input.front; 351 352 static if (isForwardRange!R && 353 __traits(isCopyable, R)) /+ TODO: should save be allowed for non-copyable? +/ 354 auto save() @property => typeof(this)(_input.save); 355 } 356 357 /+ TODO: move these hidden behind template defs of takeUnique +/ 358 import core.internal.traits : Unqual; 359 import std.range.primitives : isInputRange, isInfinite, hasSlicing; 360 361 /// Unique take. 362 UniqueTake!R takeUnique(R)(R input, size_t n) 363 if (is(R T == UniqueTake!T)) 364 { 365 import std.algorithm.mutation : move; 366 import std.algorithm.comparison : min; 367 return R(move(input.source), /+ TODO: remove `move` when compiler does it for us +/ 368 min(n, input._maxAvailable)); 369 } 370 371 /// ditto 372 UniqueTake!(R) takeUnique(R)(R input, size_t n) 373 if (isInputRange!(Unqual!R) && 374 (isInfinite!(Unqual!R) || 375 !hasSlicing!(Unqual!R) && 376 !is(R T == UniqueTake!T))) 377 { 378 import std.algorithm.mutation : move; 379 return UniqueTake!R(move(input), n); /+ TODO: remove `move` when compiler does it for us +/ 380 } 381 382 struct UniqueTake(Range) 383 if (isInputRange!(Unqual!Range) && 384 //take _cannot_ test hasSlicing on infinite ranges, because hasSlicing uses 385 //take for slicing infinite ranges. 386 !((!isInfinite!(Unqual!Range) && hasSlicing!(Unqual!Range)) || is(Range T == UniqueTake!T))) 387 { 388 import std.range.primitives : isForwardRange, hasAssignableElements, ElementType, hasMobileElements, isRandomAccessRange, moveFront; 389 390 private alias R = Unqual!Range; 391 392 /// User accessible in read and write 393 public R source; 394 395 private size_t _maxAvailable; 396 397 alias Source = R; 398 399 this(R source, size_t _maxAvailable) 400 { 401 import std.algorithm.mutation : move; 402 this.source = move(source); 403 this._maxAvailable = _maxAvailable; 404 } 405 406 /// Range primitives 407 bool empty() @property => _maxAvailable == 0 || source.empty; 408 409 /// ditto 410 auto ref front() @property in(!empty) => source.front; 411 412 /// ditto 413 void popFront() in(!empty) 414 { 415 source.popFront(); 416 --_maxAvailable; 417 } 418 419 static if (isForwardRange!R) 420 /// ditto 421 UniqueTake save() @property => UniqueTake(source.save, _maxAvailable); 422 423 static if (hasAssignableElements!R) 424 /// ditto 425 @property auto front(ElementType!R v) 426 in(!empty, "Attempting to assign to the front of an empty " ~ UniqueTake.stringof) 427 { 428 // This has to return auto instead of void because of Bug 4706. 429 source.front = v; 430 } 431 432 // static if (hasMobileElements!R) 433 // { 434 // /// ditto 435 // auto moveFront() 436 // { 437 // assert(!empty, 438 // "Attempting to move the front of an empty " 439 // ~ UniqueTake.stringof); 440 // return source.moveFront(); 441 // } 442 // } 443 444 static if (isInfinite!R) 445 { 446 /// ditto 447 @property size_t length() const => _maxAvailable; 448 /// ditto 449 alias opDollar = length; 450 451 //Note: Due to UniqueTake/hasSlicing circular dependency, 452 //This needs to be a restrained template. 453 /// ditto 454 auto opSlice()(size_t i, size_t j) 455 if (hasSlicing!R) 456 in(i <= j, "Invalid slice bounds") 457 in(j <= length, "Attempting to slice past the end of a " ~ UniqueTake.stringof) 458 => source[i .. j]; 459 } 460 else static if (hasLength!R) 461 { 462 /// ditto 463 @property size_t length() 464 { 465 import std.algorithm.comparison : min; 466 return min(_maxAvailable, source.length); 467 } 468 alias opDollar = length; 469 } 470 471 static if (isRandomAccessRange!R) 472 { 473 /// ditto 474 void popBack() 475 in(!empty, "Attempting to popBack() past the beginning of a " ~ UniqueTake.stringof) 476 { 477 --_maxAvailable; 478 } 479 480 /// ditto 481 @property auto ref back() 482 in(!empty, "Attempting to fetch the back of an empty " ~ UniqueTake.stringof) 483 => source[this.length - 1]; 484 485 /// ditto 486 auto ref opIndex(size_t index) 487 in(index < length, "Attempting to index out of the bounds of a " ~ UniqueTake.stringof) 488 => source[index]; 489 490 static if (hasAssignableElements!R) 491 { 492 /// ditto 493 @property auto back(ElementType!R v) 494 in(!empty, "Attempting to assign to the back of an empty " ~ UniqueTake.stringof) 495 { 496 // This has to return auto instead of void because of Bug 4706. 497 source[this.length - 1] = v; 498 } 499 500 /// ditto 501 void opIndexAssign(ElementType!R v, size_t index) 502 in(index < length, "Attempting to index out of the bounds of a " ~ UniqueTake.stringof) 503 { 504 source[index] = v; 505 } 506 } 507 508 static if (hasMobileElements!R) 509 { 510 /// ditto 511 auto moveBack() 512 in(!empty, "Attempting to move the back of an empty " ~ UniqueTake.stringof) 513 => source.moveAt(this.length - 1); 514 515 /// ditto 516 auto moveAt(size_t index) 517 in(index < length, "Attempting to index out of the bounds of a " ~ UniqueTake.stringof) 518 => source.moveAt(index); 519 } 520 } 521 522 /** 523 Access to maximal length of the range. 524 Note: the actual length of the range depends on the underlying range. 525 If it has fewer elements, it will stop before lengthMax is reached. 526 */ 527 @property size_t lengthMax() const => _maxAvailable; 528 } 529 530 /// array range 531 pure nothrow @safe @nogc unittest { 532 import std.experimental.allocator.mallocator : Mallocator; 533 import nxt.container.dynamic_array : DA = DynamicArray; 534 import std.traits : isIterable; 535 import std.range.primitives : isInputRange; 536 alias C = DA!(int, Mallocator); 537 538 auto cs = C([11, 13].s).intoUniqueRange; 539 540 alias CS = typeof(cs); 541 static assert(isInputRange!(typeof(cs))); 542 static assert(isIterable!(typeof(cs))); 543 544 assert(cs.front == 11); 545 cs.popFront(); 546 547 assert(cs.front == 13); 548 cs.popFront(); 549 550 assert(cs.empty); 551 } 552 553 import std.functional : binaryFun; 554 555 InputRange findUnique(alias pred = "a == b", InputRange, Element)(InputRange haystack, scope Element needle) 556 if (isInputRange!InputRange && 557 is (typeof(binaryFun!pred(haystack.front, needle)) : bool)) 558 { 559 for (; !haystack.empty; haystack.popFront()) 560 if (binaryFun!pred(haystack.front, needle)) 561 break; 562 import std.algorithm.mutation : move; 563 return move(haystack); 564 } 565 566 version (unittest) 567 { 568 import nxt.array_help : s; 569 import nxt.debugio : dbg; 570 import nxt.algorithm.comparison : equal; 571 } 572 573 version (unittest) { 574 import nxt.construction : dupShallow; 575 }