1 /** Array container(s) with optional sortedness via template-parameter 2 * `Ordering` and optional use of GC via `useGCAllocation`. 3 * 4 * TODO UniqueArray!(const(E)) where E has indirections 5 * 6 * TODO Support for constructing from r-value range (container) of non-copyable 7 * elements. 8 * 9 * TODO Add some way to implement lazy sorting either for the whole array (via 10 * bool flag) or completeSort at a certain offset (extra member). 11 * 12 * TODO Replace ` = void` with construction or emplace 13 * 14 * TODO Break out common logic into private `DynamicArray` and reuse with `alias 15 * this` to express StandardArray, SortedArray, SortedSetArray 16 * 17 * TODO Use std.array.insertInPlace in insert()? 18 * TODO Use std.array.replaceInPlace? 19 * 20 * TODO Use `std.algorithm.mutation.move` and `std.range.primitives.moveAt` 21 * when moving internal sub-slices 22 * 23 * TODO Add `c.insertAfter(r, x)` where `c` is a collection, `r` is a range 24 * previously extracted from `c`, and `x` is a value convertible to 25 * collection's element type. See_Also: 26 * https://forum.dlang.org/post/n3qq6e$2bis$1@digitalmars.com 27 * 28 * TODO replace qcmeman with std.experimental.allocator parameter defaulting to 29 * `Mallocator` 30 * 31 * TODO use `integer_sorting.radixSort` when element type `isSigned` or `isUnsigned` and 32 * above or below a certain threshold calculated by my benchmarks 33 * 34 * TODO Remove explicit moves when DMD std.algorithm.mutation.move calls these 35 * members for us (if they exist) 36 */ 37 module nxt.array_ex; 38 39 /// Array element ordering. 40 enum Ordering 41 { 42 unsorted, // unsorted array 43 sortedValues, // sorted array with possibly duplicate values 44 sortedUniqueSet, // sorted array with unique values 45 } 46 47 /// Is `true` iff `ordering` is sorted. 48 enum isOrdered(Ordering ordering) = ordering != Ordering.unsorted; 49 50 version(unittest) 51 { 52 import std.algorithm.iteration : map, filter; 53 import std.algorithm.comparison : equal; 54 import std.conv : to; 55 import std.meta : AliasSeq; 56 import core.internal.traits : Unqual; 57 import nxt.dbgio : dbg; 58 import nxt.array_help : s; 59 } 60 61 import nxt.container_traits : ContainerElementType, needsMove; 62 63 /// Is `true` iff `C` is an instance of an `Array` container. 64 template isArrayContainer(C) 65 { 66 import std.traits : isInstanceOf; 67 enum isArrayContainer = isInstanceOf!(Array, C); 68 } 69 70 /// Semantics of copy construction and copy assignment. 71 enum Assignment 72 { 73 disabled, /// for reference counting use `std.typecons.RefCounted`. for safe slicing use `borrown` 74 move, /// only move construction allowed 75 copy /// always copy (often not the desirable) 76 } 77 78 /** Array of value types `E` with optional sortedness/ordering. 79 * 80 * Always `@safe pure nothrow @nogc` when possible. 81 * 82 * `Assignment` either 83 * - is disabled 84 * - does Rust-style move, or 85 * - does C++-style copying 86 * 87 * Params: 88 * GCAllocation = `true` iff `GC.malloc` is used for store allocation, 89 * otherwise C's `{m,ce,re}alloc()` is used. 90 */ 91 private struct Array(E, 92 // TODO merge these flags into one to reduce template bloat 93 Assignment assignment = Assignment.disabled, 94 Ordering ordering = Ordering.unsorted, 95 bool useGCAllocation = false, 96 CapacityType = size_t, // see also https://github.com/izabera/s 97 alias less = "a < b") // TODO move out of this definition and support only for the case when `ordering` is not `Ordering.unsorted` 98 if (is(CapacityType == ulong) || // 3 64-bit words 99 is(CapacityType == uint)) // 2 64-bit words 100 { 101 import core.internal.traits : hasElaborateDestructor; 102 import core.lifetime : emplace, move, moveEmplace; 103 import std.algorithm.mutation : moveEmplaceAll; 104 import std.range.primitives : isInputRange, isInfinite, ElementType; 105 import std.traits : isIterable, isAssignable, Unqual, isArray, isScalarType, hasIndirections, TemplateOf; 106 import std.functional : binaryFun; 107 import std.meta : allSatisfy; 108 109 import nxt.qcmeman : malloc, calloc, realloc, free, gc_addRange, gc_removeRange; 110 111 private template shouldAddGCRange(T) 112 { 113 import std.traits : hasIndirections, isInstanceOf; 114 enum shouldAddGCRange = hasIndirections!T && !isInstanceOf!(Array, T); // TODO unify to container_traits.shouldAddGCRange 115 } 116 117 /// Mutable element type. 118 private alias MutableE = Unqual!E; 119 120 /// Template for type of `this`. 121 private alias ThisTemplate = TemplateOf!(typeof(this)); 122 123 /// Same type as this but with mutable element type. 124 private alias MutableThis = ThisTemplate!(MutableE, assignment, ordering, useGCAllocation, CapacityType, less); 125 126 static if (useGCAllocation || // either we asked for allocation 127 shouldAddGCRange!E) // or we need GC ranges 128 { 129 import core.memory : GC; 130 } 131 132 /// Is `true` iff `Array` can be interpreted as a narrow D `string` or `wstring`. 133 private enum isNarrowString = (is(MutableE == char) || 134 is(MutableE == wchar)); 135 136 static if (isOrdered!ordering) 137 { 138 static assert(!isNarrowString, "A narrow string cannot be an ordered array because it's not random access'"); 139 } 140 141 alias comp = binaryFun!less; //< comparison 142 143 /// Create a empty array. 144 // this(typeof(null)) nothrow 145 // { 146 // version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 147 // // nothing needed, rely on default initialization of data members 148 // } 149 150 /// Returns: an array of length `initialLength` with all elements default-initialized to `ElementType.init`. 151 static typeof(this) withLength(size_t initialLength) 152 @trusted 153 { 154 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 155 156 debug { typeof(return) that; } 157 else { typeof(return) that = void; } 158 159 // TODO functionize: 160 if (initialLength > smallCapacity) 161 { 162 emplace!Large(&that._large, initialLength, initialLength, true); // no elements so we need to zero 163 } 164 else 165 { 166 that._small.length = cast(ubyte)initialLength; 167 } 168 169 version(showCtors) dbg("EXITING: ", __PRETTY_FUNCTION__); 170 return that; 171 } 172 173 /// Returns: an array with initial capacity `initialCapacity`. 174 static typeof(this) withCapacity(size_t initialCapacity) 175 @trusted 176 { 177 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 178 179 debug { typeof(return) that; } 180 else { typeof(return) that = void; } 181 182 if (initialCapacity > smallCapacity) 183 { 184 emplace!Large(&that._large, initialCapacity, 0, false); 185 } 186 else 187 { 188 that._small.length = 0; 189 } 190 191 version(showCtors) dbg("EXITING: ", __PRETTY_FUNCTION__); 192 return that; 193 } 194 195 /// Returns: an array of one element `element`. 196 static typeof(this) withElement(E element) 197 @trusted 198 { 199 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 200 201 debug { typeof(return) that; } 202 else { typeof(return) that = void; } 203 204 // TODO functionize: 205 enum initialLength = 1; 206 if (initialLength > smallCapacity) 207 { 208 emplace!Large(&that._large, initialLength, initialLength, false); 209 } 210 else 211 { 212 emplace!Small(&that._small, initialLength, false); 213 } 214 215 // move element 216 static if (__traits(isCopyable, E)) 217 { 218 that._mptr[0] = element; 219 } 220 else static if (!shouldAddGCRange!E) 221 { 222 moveEmplace(*cast(MutableE*)&element, // TODO can we prevent this cast? 223 that._mptr[0]); // safe to cast away constness when no indirections 224 } 225 else 226 { 227 moveEmplace(element, that._mptr[0]); // TODO remove `move` when compiler does it for us 228 } 229 230 return that; 231 } 232 233 /// Returns: an array of `Us.length` number of elements set to `elements`. 234 static typeof(this) withElements(Us...)(Us elements) 235 @trusted 236 { 237 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 238 239 debug { typeof(return) that; } 240 else { typeof(return) that = void; } 241 242 // TODO functionize: 243 enum initialLength = Us.length; 244 if (initialLength > smallCapacity) 245 { 246 emplace!Large(&that._large, initialLength, initialLength, false); 247 } 248 else 249 { 250 emplace!Small(&that._small, initialLength, false); 251 } 252 253 // move elements 254 foreach (immutable i, ref element; elements) 255 { 256 static if (!shouldAddGCRange!E) 257 { 258 moveEmplace(*cast(MutableE*)&element, 259 that._mptr[i]); // safe to cast away constness when no indirections 260 } 261 else 262 { 263 moveEmplace(element, that._mptr[i]); // TODO remove `move` when compiler does it for us 264 } 265 } 266 267 static if (isOrdered!ordering) 268 { 269 that.sortElements!comp(); 270 } 271 272 return that; 273 } 274 275 static if (assignment == Assignment.copy) 276 { 277 /// Copy construction. 278 this(this) @trusted 279 { 280 version(showCtors) dbg("Copy ctor: ", typeof(this).stringof); 281 if (isLarge) // only large case needs special treatment 282 { 283 auto rhs_storePtr = _large.ptr; // save store pointer 284 _large.setCapacity(this.length); // pack by default 285 // _large.length already copied 286 _large.ptr = allocate(this.length, false); 287 foreach (immutable i; 0 .. this.length) 288 { 289 _large.ptr[i] = rhs_storePtr[i]; 290 } 291 } 292 } 293 294 /// Copy assignment. 295 void opAssign(typeof(this) rhs) @trusted 296 { 297 version(showCtors) dbg("Copy assign: ", typeof(this).stringof); 298 // self-assignment may happen when assigning derefenced pointer 299 if (isLarge) // large = ... 300 { 301 if (rhs.isLarge) // large = large 302 { 303 // TODO functionize to Large.opAssign(Large rhs): 304 if (_large.ptr != rhs._large.ptr) // if not self assignment 305 { 306 _large.length = rhs._large.length; 307 reserve(rhs._large.length); 308 foreach (immutable i; 0 .. rhs._large.length) 309 { 310 _large.ptr[i] = rhs._large.ptr[i]; 311 } 312 } 313 } 314 else // large = small 315 { 316 { // make it small 317 clear(); // clear large storage 318 _large.isLarge = false; // TODO needed? 319 } 320 _small = rhs._small; // small 321 } 322 } 323 else // small = ... 324 { 325 if (rhs.isLarge) // small = large 326 { 327 { // make it large 328 clear(); // clear small storage 329 _large.isLarge = true; // TODO needed? 330 } 331 // TODO functionize to Large.opAssign(Large rhs): 332 if (_large.ptr != rhs._large.ptr) // if not self assignment 333 { 334 _large.length = rhs._large.length; 335 reserve(rhs._large.length); 336 foreach (immutable i; 0 .. rhs._large.length) 337 { 338 _large.ptr[i] = rhs._large.ptr[i]; 339 } 340 } 341 } 342 else // small = small 343 { 344 _small = rhs._small; 345 } 346 } 347 } 348 } 349 else static if (assignment == Assignment.disabled) 350 { 351 @disable this(this); 352 } 353 else static if (assignment == Assignment.move) 354 { 355 /// Copy ctor moves. 356 this(typeof(this) rhs) @trusted 357 { 358 version(showCtors) dbg("Copying: ", typeof(this).stringof); 359 assert(!isBorrowed); 360 moveEmplace(rhs, this); // TODO remove `move` when compiler does it for us 361 } 362 363 /// Assignment moves. 364 void opAssign(typeof(this) rhs) @trusted 365 { 366 assert(!isBorrowed); 367 import std.algorith.mutation : move; 368 move(rhs, this); // TODO remove `move` when compiler does it for us 369 } 370 } 371 372 static if (__traits(isCopyable, E)) 373 { 374 /// Returns: shallow duplicate of `this`. 375 @property MutableThis dup() const @trusted // `MutableThis` mimics behaviour of `dup` for builtin D arrays 376 { 377 debug { typeof(return) that; } 378 else { typeof(return) that = void; } 379 380 if (isLarge) 381 { 382 emplace!(that.Large)(&that._large, _large.length, _large.length, false); 383 foreach (immutable i; 0 .. _large.length) 384 { 385 // TODO is there a more standardized way of solving this other than this hacky cast? 386 that._large.ptr[i] = (cast(E*)_large.ptr)[i]; 387 } 388 } 389 else 390 { 391 emplace!(that.Small)(&that._small, _small.length, false); 392 // TODO is there a more standardized way of solving this other than this hacky cast? 393 that._small.elms[0 .. _small.length] = (cast(E[_small.elms.length])_small.elms)[0 .. _small.length]; // copy elements 394 } 395 return that; 396 } 397 } 398 399 bool opEquals(in ref typeof(this) rhs) const 400 @trusted 401 { 402 static if (__traits(isCopyable, E)) 403 { 404 return this[] == rhs[]; // TODO fix DMD to make this work for non-copyable aswell 405 } 406 else 407 { 408 if (this.length != rhs.length) { return false; } 409 foreach (immutable i; 0 .. this.length) 410 { 411 if (this.ptr[i] != rhs.ptr[i]) { return false; } 412 } 413 return true; 414 } 415 } 416 bool opEquals(in typeof(this) rhs) const 417 @trusted 418 { 419 static if (__traits(isCopyable, E)) 420 { 421 return this[] == rhs[]; // TODO fix DMD to make this work for non-copyable aswell 422 } 423 else 424 { 425 if (this.length != rhs.length) { return false; } 426 foreach (immutable i; 0 .. this.length) 427 { 428 if (this.ptr[i] != rhs.ptr[i]) { return false; } 429 } 430 return true; 431 } 432 } 433 434 /// Compare with range `R` with comparable element type. 435 pragma(inline, true) 436 bool opEquals(R)(R rhs) const 437 if (isInputRange!R && !isInfinite!R) 438 { 439 return opSlice.equal(rhs); 440 } 441 442 /// Calculate D associative array (AA) key hash. 443 hash_t toHash() const // cannot currently be template-lazy 444 nothrow @trusted 445 { 446 pragma(msg, "WARNING: using toHash() when we should use toDigest instead"); 447 import core.internal.hash : hashOf; 448 static if (__traits(isCopyable, E)) 449 { 450 return this.length ^ hashOf(slice()); 451 } 452 else 453 { 454 typeof(return) hash = this.length; 455 foreach (immutable i; 0 .. this.length) 456 { 457 hash ^= this.ptr[i].hashOf; 458 } 459 return hash; 460 } 461 } 462 463 /** Construct from InputRange `values`. 464 If `values` are sorted `assumeSortedParameter` is `true`. 465 466 TODO Have `assumeSortedParameter` only when `isOrdered!ordering` is true 467 */ 468 this(R)(R values, bool assumeSortedParameter = false) 469 @trusted 470 @("complexity", "O(n*log(n))") 471 if (isIterable!R) 472 { 473 version(showCtors) dbg("ENTERING: smallCapacity:", smallCapacity, " @", __PRETTY_FUNCTION__); 474 475 // append new data 476 import std.range.primitives : hasLength; 477 static if (hasLength!R) 478 { 479 // TODO choose large or small depending on values.length 480 _small.isLarge = false; 481 _small.length = 0; 482 483 reserve(values.length); // fast reserve 484 setOnlyLength(values.length); 485 486 // static if (__traits(isRef)) 487 // { 488 // // TODO dup elements 489 // } 490 // else 491 // { 492 // // TODO move elements 493 // } 494 495 size_t i = 0; 496 foreach (ref value; move(values)) // TODO remove `move` when compiler does it for us 497 { 498 // TODO functionize: 499 static if (needsMove!(typeof(value))) 500 { 501 moveEmplace(value, _mptr[i++]); 502 } 503 else 504 { 505 _mptr[i++] = value; 506 } 507 } 508 } 509 else 510 { 511 // always start small 512 _small.isLarge = false; 513 _small.length = 0; 514 515 size_t i = 0; 516 foreach (ref value; move(values)) // TODO remove `move` when compiler does it for us 517 { 518 reserve(i + 1); // slower reserve 519 // TODO functionize: 520 static if (needsMove!(typeof(value))) 521 { 522 moveEmplace(value, _mptr[i++]); 523 } 524 else 525 { 526 _mptr[i++] = value; 527 } 528 setOnlyLength(i); // must be set here because correct length is needed in reserve call above in this same scope 529 } 530 } 531 532 if (!assumeSortedParameter) 533 { 534 static if (isOrdered!ordering) 535 { 536 sortElements!comp(); 537 } 538 static if (ordering == Ordering.sortedUniqueSet) 539 { 540 import std.algorithm.iteration : uniq; 541 size_t j = 0; 542 foreach (ref e; uniq(slice)) 543 { 544 auto ePtr = &e; 545 const separate = ePtr != &_mptr[j]; 546 if (separate) 547 { 548 move(*cast(MutableE*)ePtr, _mptr[j]); 549 } 550 ++j; 551 } 552 shrinkTo(j); 553 } 554 } 555 556 version(showCtors) dbg("EXITING: ", __PRETTY_FUNCTION__); 557 } 558 559 /// Sort all elements in-place regardless of `ordering`. 560 private void sortElements(alias comp_)() 561 @trusted 562 { 563 import std.algorithm.sorting : sort; 564 sort!comp_(_mptr[0 .. length]); 565 } 566 567 /// Reserve room for `newCapacity`. 568 void reserve(size_t newCapacity) 569 pure @trusted 570 { 571 assert(!isBorrowed); 572 if (newCapacity <= capacity) { return; } 573 if (isLarge) 574 { 575 static if (shouldAddGCRange!E) 576 { 577 gc_removeRange(_mptr); 578 } 579 import std.math : nextPow2; 580 reallocateLargeStoreAndSetCapacity(newCapacity.nextPow2); 581 static if (shouldAddGCRange!E) 582 { 583 gc_addRange(_mptr, _large.capacity * E.sizeof); 584 } 585 } 586 else 587 { 588 if (newCapacity > smallCapacity) // convert to large 589 { 590 auto tempLarge = Large(newCapacity, length, false); 591 592 // move small to temporary large 593 foreach (immutable i; 0 .. length) 594 { 595 moveEmplace(_small._mptr[i], 596 tempLarge._mptr[i]); 597 } 598 599 static if (hasElaborateDestructor!E) 600 { 601 destroyElements(); 602 } 603 604 // TODO functionize: 605 { // make this large 606 moveEmplace(tempLarge, _large); 607 } 608 609 assert(isLarge); 610 } 611 else 612 { 613 // staying small 614 } 615 } 616 } 617 618 /// Pack/Compress storage. 619 void compress() 620 pure @trusted 621 { 622 assert(!isBorrowed); 623 if (isLarge) 624 { 625 if (this.length) 626 { 627 if (this.length <= smallCapacity) 628 { 629 Small tempSmall = Small(length, false); 630 631 // move elements to temporary small. TODO make moveEmplaceAll work on char[],char[] and use 632 foreach (immutable i; 0 .. length) 633 { 634 moveEmplace(_large._mptr[i], 635 tempSmall._mptr[i]); 636 } 637 638 // free existing large data 639 static if (shouldAddGCRange!E) 640 { 641 gc_removeRange(_mptr); 642 } 643 static if (useGCAllocation) 644 { 645 GC.free(_mptr); 646 } 647 else 648 { 649 free(_mptr); 650 } 651 652 moveEmplace(tempSmall, _small); 653 } 654 else 655 { 656 if (_large.capacity != this.length) 657 { 658 static if (shouldAddGCRange!E) 659 { 660 gc_removeRange(_mptr); 661 } 662 reallocateLargeStoreAndSetCapacity(this.length); 663 static if (shouldAddGCRange!E) 664 { 665 gc_addRange(_mptr, _large.capacity * E.sizeof); 666 } 667 } 668 } 669 } 670 else // if empty 671 { 672 // free data 673 static if (shouldAddGCRange!E) 674 { 675 gc_removeRange(_mptr); 676 } 677 static if (useGCAllocation) 678 { 679 GC.free(_mptr); 680 } 681 else 682 { 683 free(_mptr); 684 } 685 _large.capacity = 0; 686 _large.ptr = null; 687 } 688 } 689 } 690 /// ditto 691 alias pack = compress; 692 693 /// Reallocate storage. TODO move to Large.reallocateAndSetCapacity 694 private void reallocateLargeStoreAndSetCapacity(size_t newCapacity) pure @trusted 695 { 696 pragma(inline, true); 697 _large.setCapacity(newCapacity); 698 static if (useGCAllocation) 699 { 700 _large.ptr = cast(E*)GC.realloc(_mptr, E.sizeof * _large.capacity); 701 } 702 else // @nogc 703 { 704 _large.ptr = cast(E*)realloc(_mptr, E.sizeof * _large.capacity); 705 assert(_large.ptr, "Reallocation failed"); 706 } 707 } 708 709 /// Destruct. 710 ~this() @trusted @nogc 711 { 712 pragma(inline, true); 713 assert(!isBorrowed); 714 if (isLarge) 715 { 716 debug assert(_large.ptr != _ptrMagic, "Double free."); // trigger fault for double frees 717 } 718 release(); 719 if (isLarge) 720 { 721 debug _large.ptr = _ptrMagic; // tag as freed 722 } 723 } 724 725 /// Empty. 726 void clear() @nogc 727 { 728 pragma(inline, true); 729 assert(!isBorrowed); 730 release(); 731 resetInternalData(); 732 } 733 /// ditto 734 pragma(inline, true) 735 void opAssign(typeof(null)) 736 { 737 clear(); 738 } 739 740 /// Destroy elements. 741 static if (hasElaborateDestructor!E) 742 { 743 private void destroyElements() @trusted 744 { 745 foreach (immutable i; 0 .. this.length) 746 { 747 .destroy(_mptr[i]); 748 } 749 } 750 } 751 752 /// Release internal store. 753 private void release() @trusted @nogc 754 { 755 static if (hasElaborateDestructor!E) 756 { 757 destroyElements(); 758 } 759 if (isLarge) 760 { 761 static if (shouldAddGCRange!E) 762 { 763 gc_removeRange(_large.ptr); 764 } 765 static if (useGCAllocation) 766 { 767 GC.free(_large.ptr); 768 } 769 else // @nogc 770 { 771 static if (!shouldAddGCRange!E) 772 { 773 free(cast(MutableE*)_large.ptr); // safe to case away constness 774 } 775 else 776 { 777 free(_large.ptr); 778 } 779 } 780 } 781 } 782 783 /// Reset internal data. 784 pragma(inline, true) 785 private void resetInternalData() @trusted pure @nogc 786 { 787 if (isLarge) 788 { 789 _large.ptr = null; 790 _large.length = 0; 791 _large.capacity = 0; 792 } 793 else 794 { 795 _small.length = 0; // fast discardal 796 } 797 } 798 799 /// Is `true` if `U` can be assign to the element type `E` of `this`. 800 enum isElementAssignable(U) = isAssignable!(E, U); 801 802 /** Removal doesn't need to care about ordering. */ 803 ContainerElementType!(typeof(this), E) removeAt(size_t index) 804 @trusted 805 @("complexity", "O(length)") 806 { 807 assert(!isBorrowed); 808 assert(index < this.length); 809 auto value = move(_mptr[index]); 810 811 // TODO use this instead: 812 // immutable si = index + 1; // source index 813 // immutable ti = index; // target index 814 // immutable restLength = this.length - (index + 1); 815 // moveEmplaceAll(_mptr[si .. si + restLength], 816 // _mptr[ti .. ti + restLength]); 817 818 foreach (immutable i; 0 .. this.length - (index + 1)) // each element index that needs to be moved 819 { 820 immutable si = index + i + 1; // source index 821 immutable ti = index + i; // target index 822 moveEmplace(_mptr[si], // TODO remove `move` when compiler does it for us 823 _mptr[ti]); 824 } 825 826 decOnlyLength(); 827 return move(value); // TODO remove `move` when compiler does it for us 828 } 829 830 /** Removal doesn't need to care about ordering. */ 831 pragma(inline, true) 832 ContainerElementType!(typeof(this), E) popFront() 833 @trusted 834 @("complexity", "O(length)") 835 { 836 return removeAt(0); 837 } 838 839 /** Removal doesn't need to care about ordering. */ 840 pragma(inline) 841 void popBack() @("complexity", "O(1)") 842 { 843 assert(!isBorrowed); 844 assert(!empty); 845 decOnlyLength(); 846 } 847 848 /** Pop back element and return it. */ 849 pragma(inline) 850 E backPop() 851 @trusted 852 { 853 assert(!isBorrowed); 854 assert(!empty); 855 decOnlyLength(); 856 // TODO functionize: 857 static if (needsMove!E) 858 { 859 return move(_mptr[this.length]); // move is indeed need here 860 } 861 else 862 { 863 return _mptr[this.length]; // no move needed 864 } 865 } 866 867 /** Pop last `count` back elements. */ 868 pragma(inline, true) 869 void popBackN(size_t count) @("complexity", "O(1)") 870 { 871 assert(!isBorrowed); 872 shrinkTo(this.length - count); 873 } 874 875 static if (!isOrdered!ordering) // for unsorted arrays 876 { 877 /// Push back (append) `values`. 878 pragma(inline) void insertBack(Us...)(Us values) 879 @trusted 880 @("complexity", "O(1)") 881 if (values.length >= 1 && 882 allSatisfy!(isElementAssignable, Us)) 883 { 884 assert(!isBorrowed); 885 immutable newLength = this.length + values.length; 886 reserve(newLength); 887 foreach (immutable i, ref value; values) // `ref` so we can `move` 888 { 889 // TODO functionize: 890 static if (needsMove!(typeof(value))) 891 { 892 moveEmplace(*cast(MutableE*)&value, _mptr[this.length + i]); 893 } 894 else 895 { 896 _mptr[this.length + i] = value; 897 } 898 } 899 setOnlyLength(this.length + values.length); 900 } 901 902 /// ditto 903 void insertBack(R)(R values) @("complexity", "O(values.length)") @trusted 904 if (isInputRange!R && !isInfinite!R && 905 !(isArray!R) && 906 !(isArrayContainer!R) && 907 isElementAssignable!(ElementType!R)) 908 { 909 assert(!isBorrowed); 910 import std.range.primitives : hasLength; 911 static if (hasLength!R) 912 { 913 const nextLength = this.length + values.length; 914 reserve(nextLength); 915 size_t i = 0; 916 foreach (ref value; values) // `ref` so we can `move` 917 { 918 // TODO functionize: 919 static if (needsMove!(typeof(value))) 920 { 921 moveEmplace(*cast(Mutable!E*)&value, _mptr[this.length + i]); 922 } 923 else 924 { 925 _mptr[this.length + i] = value; 926 } 927 ++i; 928 } 929 setOnlyLength(nextLength); 930 } 931 else 932 { 933 foreach (ref value; values) // `ref` so we can `move` 934 { 935 insertBack(value); 936 } 937 } 938 } 939 940 /// ditto. 941 void insertBack(A)(A values) @trusted @("complexity", "O(values.length)") 942 if (isArray!A && 943 (is(MutableE == Unqual!(typeof(A.init[0]))) || // for narrow strings 944 isElementAssignable!(ElementType!A))) 945 { 946 assert(!isBorrowed); 947 static if (is(A == immutable(E)[])) 948 { 949 // immutable array cannot overlap with mutable array container 950 // data; no need to check for overlap with `overlaps()` 951 reserve(this.length + values.length); 952 _mptr[this.length .. this.length + values.length] = values[]; 953 setOnlyLength(this.length + values.length); 954 } 955 else 956 { 957 import nxt.overlapping : overlaps; 958 if (this.ptr == values[].ptr) // called for instances as: `this ~= this` 959 { 960 reserve(2*this.length); 961 foreach (immutable i; 0 .. this.length) 962 { 963 _mptr[this.length + i] = ptr[i]; // needs copying 964 } 965 setOnlyLength(2 * this.length); 966 } 967 else if (overlaps(this[], values[])) 968 { 969 assert(0, `TODO Handle overlapping arrays`); 970 } 971 else 972 { 973 reserve(this.length + values.length); 974 static if (is(MutableE == Unqual!(ElementType!A))) // TODO also when `E[]` is `A[]` 975 { 976 _mptr[this.length .. this.length + values.length] = values[]; 977 } 978 else 979 { 980 foreach (immutable i, ref value; values) 981 { 982 _mptr[this.length + i] = value; 983 } 984 } 985 setOnlyLength(this.length + values.length); 986 } 987 } 988 } 989 990 /// ditto. 991 void insertBack(A)(in ref A values) @trusted @("complexity", "O(values.length)") // TODO `in` parameter qualifier doesn't work here. Compiler bug? 992 if (isArrayContainer!A && 993 (is(MutableE == Unqual!(typeof(A.init[0]))) || // for narrow strings 994 isElementAssignable!(ElementType!A))) 995 { 996 insertBack(values[]); 997 } 998 alias put = insertBack; // OutputRange support 999 1000 1001 // NOTE these separate overloads of opOpAssign are needed because one 1002 // `const ref`-parameter-overload doesn't work because of compiler bug 1003 // with: `this(this) @disable` 1004 pragma(inline) 1005 void opOpAssign(string op, Us...)(Us values) 1006 if (op == "~" && 1007 values.length >= 1 && 1008 allSatisfy!(isElementAssignable, Us)) 1009 { 1010 assert(!isBorrowed); 1011 insertBack(move(values)); // TODO remove `move` when compiler does it for us 1012 // static if (values.length == 1) 1013 // { 1014 // import std.traits : hasIndirections; 1015 // static if (hasIndirections!(Us[0])) 1016 // { 1017 // insertBack(move(values)); // TODO remove `move` when compiler does it for us 1018 // } 1019 // else 1020 // { 1021 // insertBack(move(cast(Unqual!(Us[0]))values[0])); // TODO remove `move` when compiler does it for us 1022 // } 1023 // } 1024 // else 1025 // { 1026 // insertBack(move(values)); // TODO remove `move` when compiler does it for us 1027 // } 1028 } 1029 1030 pragma(inline) 1031 void opOpAssign(string op, R)(R values) 1032 if (op == "~" && 1033 isInputRange!R && 1034 !isInfinite!R && 1035 allSatisfy!(isElementAssignable, ElementType!R)) 1036 { 1037 assert(!isBorrowed); 1038 // TODO use move(values) 1039 insertBack(values); // TODO remove `move` when compiler does it for us 1040 } 1041 1042 pragma(inline, true) 1043 void opOpAssign(string op, A)(ref A values) 1044 if (op == "~" && 1045 isArrayContainer!A && 1046 isElementAssignable!(ElementType!A)) 1047 { 1048 assert(!isBorrowed); 1049 insertBack(values); 1050 } 1051 } 1052 1053 import searching_ex : containsStoreIndex; // TODO this is redundant but elides rdmd dependency error for array_ex.d 1054 1055 static if (isOrdered!ordering) 1056 { 1057 import std.range : SearchPolicy, assumeSorted; 1058 1059 /// Returns: `true` iff this contains `value`. 1060 bool contains(U)(U value) const @nogc @("complexity", "O(log(length))") 1061 { 1062 return this[].contains(value); // reuse `SortedRange.contains` 1063 } 1064 1065 /** Wrapper for `std.range.SortedRange.lowerBound` when this `ordering` is sorted. */ 1066 auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, U)(U e) inout @("complexity", "O(log(length))") 1067 { 1068 return this[].lowerBound!sp(e); // reuse `SortedRange.lowerBound` 1069 } 1070 1071 /** Wrapper for `std.range.SortedRange.upperBound` when this `ordering` is sorted. */ 1072 auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, U)(U e) inout @("complexity", "O(log(length))") 1073 { 1074 return this[].upperBound!sp(e); // reuse `SortedRange.upperBound` 1075 } 1076 1077 static if (ordering == Ordering.sortedUniqueSet) 1078 { 1079 /** Inserts several `values` into `this` ordered set. 1080 1081 Returns: `bool`-array with same length as `values`, where i:th 1082 `bool` value is set if `value[i]` wasn't previously in `this`. 1083 */ 1084 bool[Us.length] insertMany(SearchPolicy sp = SearchPolicy.binarySearch, Us...)(Us values) @("complexity", "O(length)") 1085 if (values.length >= 1 && 1086 allSatisfy!(isElementAssignable, Us)) 1087 in 1088 { 1089 assert(!isBorrowed); 1090 1091 // assert no duplicates in `values` 1092 import std.range.primitives : empty; 1093 import std.algorithm.searching : findAdjacent; 1094 import std.algorithm.sorting : sort; 1095 1096 // TODO functionize or use other interface in pushing `values` 1097 import std.traits : CommonType; 1098 CommonType!Us[Us.length] valuesArray; 1099 foreach (immutable i, const ref value; values) 1100 { 1101 valuesArray[i] = value; 1102 } 1103 assert(sort(valuesArray[]).findAdjacent.empty, 1104 "Parameter `values` must not contain duplicate elements"); 1105 } 1106 do 1107 { 1108 static if (values.length == 1) // faster because `contains()` followed by `completeSort()` searches array twice 1109 { 1110 import nxt.searching_ex : containsStoreIndex; 1111 size_t index; 1112 if (slice.assumeSorted!comp.containsStoreIndex!sp(values, index)) // faster than `completeSort` for single value 1113 { 1114 return [false]; 1115 } 1116 else 1117 { 1118 insertAtIndexHelper(index, values); 1119 return [true]; 1120 } 1121 } 1122 else 1123 { 1124 import std.algorithm.sorting : completeSort; 1125 1126 debug { typeof(return) hits; } 1127 else { typeof(return) hits = void; } 1128 1129 size_t expandedLength = 0; 1130 immutable initialLength = this.length; 1131 foreach (immutable i, ref value; values) 1132 { 1133 // TODO reuse completeSort with uniqueness handling? 1134 static if (values.length == 1) 1135 { 1136 // TODO reuse single parameter overload linearUniqueInsert() and return 1137 } 1138 else 1139 { 1140 // TODO reuse completeSort with uniqueness handling? 1141 } 1142 hits[i] = !this[0 .. initialLength].contains(value); 1143 if (hits[i]) 1144 { 1145 insertBackHelper(value); // NOTE: append but don't yet sort 1146 ++expandedLength; 1147 } 1148 } 1149 1150 if (expandedLength != 0) 1151 { 1152 immutable ix = this.length - expandedLength; 1153 // TODO use `_mptr` here instead and functionize to @trusted helper function 1154 completeSort!comp(slice[0 .. ix].assumeSorted!comp, 1155 slice[ix .. this.length]); 1156 } 1157 return hits; 1158 } 1159 } 1160 } 1161 else static if (ordering == Ordering.sortedValues) 1162 { 1163 /** Inserts `values`. */ 1164 void insertMany(SearchPolicy sp = SearchPolicy.binarySearch, Us...)(Us values) @("complexity", "O(log(length))") 1165 if (values.length >= 1 && 1166 allSatisfy!(isElementAssignable, Us)) 1167 { 1168 assert(!isBorrowed); 1169 1170 // TODO add optimization for values.length == 2 1171 static if (values.length == 1) 1172 { 1173 import nxt.searching_ex : containsStoreIndex; 1174 size_t index; 1175 if (!slice.assumeSorted!comp.containsStoreIndex!sp(values, index)) // faster than `completeSort` for single value 1176 { 1177 insertAtIndexHelper(index, values); 1178 } 1179 } 1180 else 1181 { 1182 insertBackHelper(values); // simpler because duplicates are allowed 1183 immutable ix = this.length - values.length; 1184 import std.algorithm.sorting : completeSort; 1185 completeSort!comp(_mptr[0 .. ix].assumeSorted!comp, 1186 _mptr[ix .. this.length]); 1187 } 1188 } 1189 } 1190 } 1191 else 1192 { 1193 /** Insert element(s) `values` at array offset `index`. */ 1194 pragma(inline, true) 1195 void insertAtIndex(Us...)(size_t index, Us values) @("complexity", "O(length)") 1196 if (values.length >= 1 && 1197 allSatisfy!(isElementAssignable, Us)) 1198 { 1199 assert(!isBorrowed); 1200 insertAtIndexHelper(index, values); 1201 } 1202 1203 /** Insert element(s) `values` at the beginning. */ 1204 pragma(inline, true) 1205 void pushFront(Us...)(Us values) @("complexity", "O(length)") 1206 if (values.length >= 1 && 1207 allSatisfy!(isElementAssignable, Us)) 1208 { 1209 insertAtIndex(0, values); 1210 } 1211 1212 alias prepend = pushFront; 1213 1214 import nxt.traits_ex : isComparable; 1215 static if (__traits(isCopyable, E) && 1216 !is(E == char) && 1217 !is(E == wchar) && 1218 isComparable!E) 1219 { 1220 /// Returns: a sorted array copy. 1221 Array!(E, assignment, ordering.sortedValues, useGCAllocation, CapacityType, less_) toSortedArray(alias less_ = "a < b")() const 1222 { 1223 return typeof(return)(slice); 1224 } 1225 /// Returns: a sorted set array copy. 1226 Array!(E, assignment, ordering.sortedUniqueSet, useGCAllocation, CapacityType, less_) toSortedSetArray(alias less_ = "a < b")() const 1227 { 1228 return typeof(return)(slice); 1229 } 1230 } 1231 } 1232 1233 /** Helper function used externally for unsorted and internally for sorted. */ 1234 private void insertAtIndexHelper(Us...)(size_t index, Us values) @trusted @("complexity", "O(length)") 1235 { 1236 reserve(this.length + values.length); 1237 1238 // TODO factor this to robustCopy. It uses copy when no overlaps (my algorithm_em), iteration otherwise 1239 enum usePhobosCopy = false; 1240 static if (usePhobosCopy) 1241 { 1242 // TODO why does this fail? 1243 import std.algorithm.mutation : copy; 1244 copy(ptr[index .. 1245 this.length], // source 1246 _mptr[index + values.length .. 1247 this.length + values.length]); // target 1248 } 1249 else 1250 { 1251 // move second part in reverse 1252 // TODO functionize move 1253 foreach (immutable i; 0 .. this.length - index) // each element index that needs to be moved 1254 { 1255 immutable si = this.length - 1 - i; // source index 1256 immutable ti = si + values.length; // target index 1257 _mptr[ti] = ptr[si]; // TODO move construct? 1258 } 1259 } 1260 1261 // set new values 1262 foreach (immutable i, ref value; values) 1263 { 1264 ptr[index + i] = value; // TODO use range algorithm instead? 1265 } 1266 1267 setOnlyLength(this.length + values.length); 1268 } 1269 1270 private void insertBackHelper(Us...)(Us values) 1271 @trusted 1272 @("complexity", "O(1)") 1273 { 1274 const newLength = this.length + values.length; 1275 reserve(newLength); 1276 foreach (immutable i, ref value; values) 1277 { 1278 // TODO functionize: 1279 static if (hasIndirections!(typeof(value))) 1280 { 1281 moveEmplace(*cast(MutableE*)&value, 1282 _mptr[this.length + i]); 1283 } 1284 else 1285 { 1286 _mptr[this.length + i] = value; 1287 } 1288 } 1289 setOnlyLength(newLength); 1290 } 1291 1292 @property @("complexity", "O(1)") 1293 pragma(inline): 1294 1295 /// ditto 1296 static if (isOrdered!ordering) 1297 { 1298 const pure: // indexing and slicing must be `const` when ordered 1299 1300 /// Slice operator must be const when ordered. 1301 auto opSlice() return scope @trusted // TODO remove @trusted? 1302 { 1303 static if (is(E == class)) 1304 { 1305 // TODO remove this workaround when else branch works for classes 1306 return (cast(E[])slice).assumeSorted!comp; 1307 } 1308 else 1309 { 1310 return (cast(const(E)[])slice).assumeSorted!comp; 1311 } 1312 } 1313 /// ditto 1314 auto opSlice(this This)(size_t i, size_t j) return scope @trusted // TODO remove @trusted? 1315 { 1316 import std.range : assumeSorted; 1317 return (cast(const(E)[])slice[i .. j]).assumeSorted!comp; 1318 } 1319 private alias This = typeof(this); 1320 1321 /// Index operator must be const to preserve ordering. 1322 ref const(E) opIndex(size_t i) return scope @nogc @trusted 1323 { 1324 assert(i < this.length); 1325 return ptr[i]; 1326 } 1327 1328 /// Get front element (as constant reference to preserve ordering). 1329 ref const(E) front() return scope @nogc @trusted 1330 { 1331 assert(!empty); 1332 return ptr[0]; 1333 } 1334 1335 /// Get back element (as constant reference to preserve ordering). 1336 ref const(E) back() return scope @nogc @trusted 1337 { 1338 assert(!empty); 1339 return ptr[this.length - 1]; 1340 } 1341 } 1342 else 1343 { 1344 /// Set length to `newLength`. 1345 @property void length(size_t newLength) 1346 { 1347 if (newLength < length) 1348 { 1349 shrinkTo(newLength); 1350 } 1351 else 1352 { 1353 reserve(newLength); 1354 setOnlyLength(newLength); 1355 } 1356 } 1357 1358 @nogc: 1359 1360 /// Index assign operator. 1361 ref E opIndexAssign(V)(V value, size_t i) 1362 @trusted return scope 1363 { 1364 assert(!isBorrowed); 1365 assert(i < this.length); 1366 static if (isScalarType!E) 1367 ptr[i] = value; 1368 else 1369 move(*(cast(MutableE*)(&value)), _mptr[i]); // TODO is this correct? 1370 return ptr[i]; 1371 } 1372 1373 /// Slice assign operator. 1374 static if (__traits(isCopyable, E)) 1375 { 1376 void opSliceAssign(V)(V value, size_t i, size_t j) 1377 @trusted return scope 1378 { 1379 assert(!isBorrowed); 1380 assert(i <= j); 1381 assert(j <= this.length); 1382 foreach (immutable i; 0 .. this.length) 1383 { 1384 ptr[i] = value; 1385 } 1386 } 1387 } 1388 1389 pure inout: // indexing and slicing has mutable access only when unordered 1390 1391 /// Slice operator. 1392 pragma(inline) 1393 inout(E)[] opSlice() return 1394 { 1395 return this.opSlice(0, this.length); 1396 } 1397 /// ditto 1398 inout(E)[] opSlice(size_t i, size_t j) 1399 @trusted return scope 1400 { 1401 assert(i <= j); 1402 assert(j <= this.length); 1403 return ptr[i .. j]; 1404 } 1405 1406 @trusted: 1407 1408 /// Index operator. 1409 ref inout(E) opIndex(size_t i) return scope 1410 { 1411 assert(i < this.length); 1412 return ptr[i]; 1413 } 1414 1415 /// Get front element reference. 1416 ref inout(E) front() return scope 1417 { 1418 assert(!empty); 1419 return ptr[0]; 1420 } 1421 1422 /// Get back element reference. 1423 ref inout(E) back() return scope 1424 { 1425 assert(!empty); 1426 return ptr[this.length - 1]; 1427 } 1428 } 1429 1430 alias data = opSlice; // `std.array.Appender` compatibility 1431 1432 // static if (__traits(isCopyable, E)) 1433 // { 1434 // string toString() const @property @trusted pure 1435 // { 1436 // import std.array : Appender; 1437 // import std.conv : to; 1438 // Appender!string s = "["; 1439 // foreach (immutable i; 0 .. this.length) 1440 // { 1441 // if (i) { s.put(','); } 1442 // s.put(ptr[i].to!string); 1443 // } 1444 // s.put("]"); 1445 // return s.data; 1446 // } 1447 // } 1448 1449 pure: 1450 1451 /** Allocate heap regionwith `newCapacity` number of elements of type `E`. 1452 If `zero` is `true` they will be zero-initialized. 1453 */ 1454 private static MutableE* allocate(size_t newCapacity, bool zero = false) 1455 { 1456 typeof(return) ptr = null; 1457 static if (useGCAllocation) 1458 { 1459 if (zero) { ptr = cast(typeof(return))GC.calloc(newCapacity, E.sizeof); } 1460 else { ptr = cast(typeof(return))GC.malloc(newCapacity * E.sizeof); } 1461 } 1462 else // @nogc 1463 { 1464 if (zero) { ptr = cast(typeof(return))calloc(newCapacity, E.sizeof); } 1465 else { ptr = cast(typeof(return))malloc(newCapacity * E.sizeof); } 1466 assert(ptr, "Allocation failed"); 1467 } 1468 static if (shouldAddGCRange!E) 1469 { 1470 gc_addRange(ptr, newCapacity * E.sizeof); 1471 } 1472 return ptr; 1473 } 1474 1475 @nogc: 1476 1477 /// Check if empty. 1478 bool empty() const { return this.length == 0; } 1479 1480 /// Get length. 1481 size_t length() 1482 const @trusted 1483 { 1484 if (isLarge) 1485 { 1486 return _large.length; 1487 } 1488 else 1489 { 1490 return _small.length; 1491 } 1492 } 1493 alias opDollar = length; /// ditto 1494 1495 /// Decrease only length. 1496 private void decOnlyLength() 1497 @trusted 1498 { 1499 if (isLarge) 1500 { 1501 assert(_large.length); 1502 _large.length = _large.length - 1; 1503 } 1504 else 1505 { 1506 assert(_small.length); 1507 _small.length = cast(SmallLengthType)(_small.length - 1); 1508 } 1509 } 1510 1511 /// Set only length. 1512 private void setOnlyLength(size_t newLength) 1513 @trusted 1514 { 1515 if (isLarge) 1516 { 1517 _large.length = newLength; // TODO compress? 1518 } 1519 else 1520 { 1521 assert(newLength <= SmallLengthType.max); 1522 _small.length = cast(SmallLengthType)newLength; 1523 } 1524 } 1525 1526 /// Get reserved capacity of store. 1527 size_t capacity() 1528 const 1529 @trusted 1530 { 1531 if (isLarge) 1532 { 1533 return _large.capacity; 1534 } 1535 else 1536 { 1537 return smallCapacity; 1538 } 1539 } 1540 1541 /** Shrink length to `newLength`. 1542 * 1543 * If `newLength` >= `length` operation has no effect. 1544 */ 1545 pragma(inline) 1546 void shrinkTo(size_t newLength) 1547 { 1548 assert(!isBorrowed); 1549 if (newLength < length) 1550 { 1551 static if (hasElaborateDestructor!E) 1552 { 1553 dbg(length, " => ", newLength, " ", E.stringof); 1554 foreach (immutable i; newLength .. length) 1555 { 1556 .destroy(_mptr[i]); 1557 } 1558 } 1559 setOnlyLength(newLength); 1560 } 1561 } 1562 1563 /// Get internal pointer. 1564 inout(E*) ptr() 1565 inout 1566 @trusted return scope // array access is @trusted 1567 { 1568 // TODO Use cast(ET[])?: alias ET = ContainerElementType!(typeof(this), E); 1569 if (isLarge) 1570 { 1571 return _large.ptr; 1572 } 1573 else 1574 { 1575 return _small.elms.ptr; 1576 } 1577 } 1578 1579 /// Get internal pointer to mutable content. Doesn't need to be qualified with `scope`. 1580 private MutableE* _mptr() 1581 const return scope 1582 { 1583 if (isLarge) 1584 { 1585 return _large._mptr; 1586 } 1587 else 1588 { 1589 return _small._mptr; 1590 } 1591 } 1592 1593 /// Get internal slice. 1594 private auto slice() inout 1595 @trusted return scope 1596 { 1597 return ptr[0 .. this.length]; 1598 } 1599 1600 /** Magic pointer value used to detect double calls to `free`. 1601 1602 Cannot conflict with return value from `malloc` because the least 1603 significant bit is set (when the value ends with a one). 1604 */ 1605 debug private enum _ptrMagic = cast(E*)0x0C6F3C6c0f3a8471; 1606 1607 /// Returns: `true` if `this` currently uses large array storage. 1608 bool isLarge() 1609 const @trusted // trusted access to anonymous union 1610 { 1611 assert(_large.isLarge == _small.isLarge); // must always be same 1612 return _large.isLarge; 1613 } 1614 1615 /// Returns: `true` if `this` currently uses small (packed) array storage. 1616 bool isSmall() const { return !isLarge; } 1617 1618 private 1619 { 1620 private alias SmallLengthType = ubyte; 1621 1622 private enum largeSmallLengthDifference = Large.sizeof - SmallLengthType.sizeof; 1623 private enum smallCapacity = largeSmallLengthDifference / E.sizeof; 1624 private enum smallPadSize = largeSmallLengthDifference - smallCapacity*E.sizeof; 1625 } 1626 1627 /** Tag `this` borrowed. 1628 Used by wrapper logic in owned.d and borrowed.d 1629 */ 1630 void tagAsBorrowed() 1631 @trusted 1632 { 1633 if (isLarge) { _large.isBorrowed = true; } 1634 else { _small.isBorrowed = true; } 1635 } 1636 1637 /** Tag `this` as not borrowed. 1638 Used by wrapper logic in owned.d and borrowed.d 1639 */ 1640 void untagAsNotBorrowed() 1641 @trusted 1642 { 1643 if (isLarge) { _large.isBorrowed = false; } 1644 else { _small.isBorrowed = false; } 1645 } 1646 1647 /// Returns: `true` if this is borrowed 1648 bool isBorrowed() 1649 @trusted 1650 { 1651 if (isLarge) { return _large.isBorrowed; } 1652 else { return _small.isBorrowed; } 1653 } 1654 1655 private: // data 1656 enum useAlignedTaggedPointer = false; // TODO make this work when true 1657 static struct Large 1658 { 1659 static if (useAlignedTaggedPointer) 1660 { 1661 private enum lengthMax = CapacityType.max; 1662 version(LittleEndian) // see: http://forum.dlang.org/posting/zifyahfohbwavwkwbgmw 1663 { 1664 import std.bitmanip : taggedPointer; 1665 mixin(taggedPointer!(uint*, "_uintptr", // GC-allocated store pointer. See_Also: http://forum.dlang.org/post/iubialncuhahhxsfvbbg@forum.dlang.org 1666 bool, "isLarge", 1, // bit 0 1667 bool, "isBorrowed", 1, // bit 1 1668 )); 1669 1670 pragma(inline, true): 1671 @property void ptr(E* c) 1672 { 1673 assert((cast(ulong)c & 0b11) == 0); 1674 _uintptr = cast(uint*)c; 1675 } 1676 1677 @property inout(E)* ptr() inout 1678 { 1679 return cast(E*)_uintptr; 1680 } 1681 1682 CapacityType capacity; // store capacity 1683 CapacityType length; // store length 1684 } 1685 else 1686 { 1687 static assert(0, "BigEndian support and test"); 1688 } 1689 } 1690 else 1691 { 1692 private enum lengthBits = 8*CapacityType.sizeof - 2; 1693 private enum lengthMax = 2^^lengthBits - 1; 1694 1695 static if (useGCAllocation) 1696 { 1697 E* ptr; // GC-allocated store pointer. See_Also: http://forum.dlang.org/post/iubialncuhahhxsfvbbg@forum.dlang.org 1698 } 1699 else 1700 { 1701 @nogc E* ptr; // non-GC-allocated store pointer 1702 } 1703 1704 CapacityType capacity; // store capacity 1705 1706 import std.bitmanip : bitfields; // TODO replace with own logic cause this mixin costs compilation speed 1707 mixin(bitfields!(CapacityType, "length", lengthBits, 1708 bool, "isBorrowed", 1, 1709 bool, "isLarge", 1, 1710 )); 1711 } 1712 1713 pragma(inline) 1714 this(size_t initialCapacity, size_t initialLength, bool zero) 1715 { 1716 assert(initialCapacity <= lengthMax); 1717 assert(initialLength <= lengthMax); 1718 1719 setCapacity(initialCapacity); 1720 this.capacity = cast(CapacityType)initialCapacity; 1721 this.ptr = allocate(initialCapacity, zero); 1722 this.length = initialLength; 1723 this.isLarge = true; 1724 this.isBorrowed = false; 1725 } 1726 1727 pragma(inline, true): 1728 1729 void setCapacity(size_t newCapacity) 1730 { 1731 assert(newCapacity <= capacity.max); 1732 capacity = cast(CapacityType)newCapacity; 1733 } 1734 1735 MutableE* _mptr() const 1736 @trusted 1737 { 1738 return cast(typeof(return))ptr; 1739 } 1740 } 1741 1742 /// Small string storage. 1743 static struct Small 1744 { 1745 enum capacity = smallCapacity; 1746 private enum lengthBits = 8*SmallLengthType.sizeof - 2; 1747 private enum lengthMax = 2^^lengthBits - 1; 1748 1749 import std.bitmanip : bitfields; // TODO replace with own logic cause this mixin costs compilation speed 1750 static if (useAlignedTaggedPointer) 1751 { 1752 mixin(bitfields!(bool, "isLarge", 1, // defaults to false 1753 bool, "isBorrowed", 1, // default to false 1754 SmallLengthType, "length", lengthBits, 1755 )); 1756 static if (smallPadSize) { ubyte[smallPadSize] _ignoredPadding; } 1757 E[capacity] elms; 1758 } 1759 else 1760 { 1761 E[capacity] elms; 1762 static if (smallPadSize) { ubyte[smallPadSize] _ignoredPadding; } 1763 mixin(bitfields!(SmallLengthType, "length", lengthBits, 1764 bool, "isBorrowed", 1, // default to false 1765 bool, "isLarge", 1, // defaults to false 1766 )); 1767 } 1768 1769 pragma(inline) 1770 this(size_t initialLength, bool zero) 1771 { 1772 assert(initialLength <= lengthMax); 1773 1774 this.length = cast(SmallLengthType)initialLength; 1775 this.isLarge = false; 1776 this.isBorrowed = false; 1777 if (zero) 1778 { 1779 elms[] = E.init; 1780 } 1781 } 1782 1783 pragma(inline, true) 1784 MutableE* _mptr() const 1785 @trusted 1786 { 1787 return cast(typeof(return))(elms.ptr); 1788 } 1789 } 1790 1791 static assert(Large.sizeof == 1792 Small.sizeof); 1793 1794 static if (E.sizeof == 1 && 1795 size_t.sizeof == 8 && // 64-bit 1796 Small.capacity == 23) 1797 { 1798 static assert(Large.sizeof == 24); 1799 static assert(Small.sizeof == 24); 1800 } 1801 1802 union 1803 { 1804 Large _large; // indirected storage 1805 Small _small; // non-indirected storage 1806 } 1807 } 1808 1809 import std.traits : hasMember, isDynamicArray; 1810 1811 /** Return an instance of `R` with capacity `capacity`. */ 1812 R withCapacityMake(R)(size_t capacity) 1813 if (hasMember!(R, "withCapacity")) 1814 { 1815 return R.withCapacity(capacity); 1816 } 1817 /// ditto 1818 R withCapacityMake(R)(size_t capacity) 1819 if (isDynamicArray!R) 1820 { 1821 R r; 1822 // See http://forum.dlang.org/post/nupffaitocqjlamffuqi@forum.dlang.org 1823 r.reserve(capacity); 1824 return r; 1825 } 1826 1827 @safe pure nothrow unittest 1828 { 1829 immutable capacity = 10; 1830 auto x = capacity.withCapacityMake!(int[]); 1831 assert(x.capacity >= capacity); 1832 } 1833 1834 /** Return an instance of `R` of length `length`. */ 1835 R withLengthMake(R)(size_t length) 1836 if (hasMember!(R, "withLength")) 1837 { 1838 return R.withLength(length); 1839 } 1840 /// ditto 1841 R withLengthMake(R)(size_t length) 1842 if (isDynamicArray!R) 1843 { 1844 R r; 1845 r.length = length; 1846 return r; 1847 } 1848 1849 /** Return an instance of `R` containing a single element `e`. */ 1850 R withElementMake(R)(typeof(R.init[0]) e) 1851 if (hasMember!(R, "withElement")) 1852 { 1853 return R.withElement(e); 1854 } 1855 /// ditto 1856 R withElementMake(R)(typeof(R.init[0]) e) 1857 if (isDynamicArray!R) 1858 { 1859 return [e]; 1860 } 1861 1862 alias UniqueArray(E, bool useGCAllocation = false) = Array!(E, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1863 alias CopyingArray(E, bool useGCAllocation = false) = Array!(E, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1864 1865 alias SortedCopyingArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.copy, Ordering.sortedValues, useGCAllocation, size_t, less); 1866 alias SortedSetCopyingArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.copy, Ordering.sortedUniqueSet, useGCAllocation, size_t, less); 1867 1868 alias SortedUniqueArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.disabled, Ordering.sortedValues, useGCAllocation, size_t, less); 1869 alias SortedSetUniqueArray(E, bool useGCAllocation = false, alias less = "a < b") = Array!(E, Assignment.disabled, Ordering.sortedUniqueSet, useGCAllocation, size_t, less); 1870 1871 // string aliases 1872 alias UniqueString(bool useGCAllocation = false) = Array!(char, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1873 alias CopyingString(bool useGCAllocation = false) = Array!(char, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1874 alias UniqueWString(bool useGCAllocation = false) = Array!(wchar, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1875 alias CopyingWString(bool useGCAllocation = false) = Array!(wchar, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1876 alias UniqueDString(bool useGCAllocation = false) = Array!(dchar, Assignment.disabled, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1877 alias CopyingDString(bool useGCAllocation = false) = Array!(dchar, Assignment.copy, Ordering.unsorted, useGCAllocation, size_t, "a < b"); 1878 1879 @safe pure unittest 1880 { 1881 auto c = UniqueString!false(); 1882 auto w = UniqueWString!false(); 1883 auto d = UniqueDString!false(); 1884 } 1885 1886 @safe pure unittest 1887 { 1888 auto c = CopyingString!false(); 1889 auto w = CopyingWString!false(); 1890 auto d = CopyingDString!false(); 1891 } 1892 1893 @safe pure unittest 1894 { 1895 import std.conv : to; 1896 foreach (assignment; AliasSeq!(Assignment.disabled, Assignment.copy)) 1897 { 1898 foreach (Ch; AliasSeq!(char, wchar, dchar)) 1899 { 1900 alias Str = Array!(Ch, assignment); 1901 Str str_as = Str.withElement('a'); 1902 Str str_as2 = 'a'.withElementMake!Str; 1903 Str str_as3 = 'a'.withElementMake!(Ch[]); 1904 assert(str_as == str_as2); 1905 assert(str_as2 == str_as3); 1906 str_as ~= Ch('_'); 1907 assert(str_as[].equal("a_")); 1908 } 1909 } 1910 } 1911 1912 static void tester(Ordering ordering, bool supportGC, alias less)() 1913 { 1914 import std.functional : binaryFun; 1915 import std.range : iota, chain, repeat, only, ElementType; 1916 import std.algorithm : filter, map; 1917 import std.algorithm.sorting : isSorted, sort; 1918 import std.exception : assertThrown, assertNotThrown; 1919 import std.traits : isInstanceOf; 1920 import core.internal.traits : Unqual; 1921 1922 enum assignment = Assignment.copy; 1923 alias comp = binaryFun!less; //< comparison 1924 1925 alias E = int; 1926 1927 { 1928 alias A = SortedUniqueArray!E; 1929 auto x = A.withElements(0, 3, 2, 1); 1930 assert(x[].equal([0, 1, 2, 3].s[])); 1931 } 1932 1933 foreach (Ch; AliasSeq!(char, wchar, dchar)) 1934 { 1935 static if (!isOrdered!ordering || // either not ordered 1936 is(Ch == dchar)) // or not a not a narrow string 1937 { 1938 alias Str = Array!(Ch, assignment, ordering, supportGC, size_t, less); 1939 auto y = Str.withElements('a', 'b', 'c'); 1940 static assert(is(Unqual!(ElementType!Str) == Ch)); 1941 y = Str.init; 1942 1943 const(Ch)[] xs; 1944 { 1945 // immutable 1946 immutable x = Str.withElements('a', 'b', 'c'); 1947 static if (!isOrdered!ordering) 1948 { 1949 xs = x[]; // TODO should fail with DIP-1000 scope 1950 } 1951 } 1952 } 1953 } 1954 1955 foreach (Ch; AliasSeq!(char)) 1956 { 1957 static if (!isOrdered!ordering) 1958 { 1959 alias Str = Array!(Ch, assignment, ordering, supportGC, size_t, less); 1960 auto str = Str.withElements('a', 'b', 'c'); 1961 1962 static if (isOrdered!ordering) 1963 { 1964 static assert(is(Unqual!(ElementType!Str) == Ch)); 1965 } 1966 else 1967 { 1968 static assert(is(ElementType!Str == Ch)); 1969 assert(str[] == `abc`); // TODO this fails for wchar and dchar 1970 } 1971 } 1972 } 1973 1974 { 1975 alias A = Array!(int, assignment, ordering, supportGC, size_t, less); 1976 foreach (immutable n; [0, 1, 2, 3, 4, 5]) 1977 { 1978 assert(A.withLength(n).isSmall); 1979 } 1980 assert(!(A.withLength(6).isSmall)); 1981 assert((A.withLength(6).isLarge)); 1982 } 1983 1984 // test move construction 1985 { 1986 immutable maxLength = 1024; 1987 foreach (immutable n; 0 .. maxLength) 1988 { 1989 auto x = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(n); 1990 1991 // test resize 1992 static if (!isOrdered!ordering) 1993 { 1994 assert(x.length == n); 1995 x.length = n + 1; 1996 assert(x.length == n + 1); 1997 x.length = n; 1998 } 1999 2000 const ptr = x.ptr; 2001 immutable capacity = x.capacity; 2002 assert(x.length == n); 2003 2004 import std.algorithm.mutation : move; 2005 auto y = Array!(E, assignment, ordering, supportGC, size_t, less)(); 2006 move(x, y); 2007 2008 assert(x.length == 0); 2009 assert(x.capacity == x.smallCapacity); 2010 2011 assert(y.length == n); 2012 assert(y.capacity == capacity); 2013 } 2014 } 2015 2016 foreach (immutable n; chain(0.only, iota(0, 10).map!(x => 2^^x))) 2017 { 2018 import std.array : array; 2019 import std.range : radial; 2020 2021 immutable zi = cast(int)0; // zero index 2022 immutable ni = cast(int)n; // number index 2023 2024 auto fw = iota(zi, ni); // 0, 1, 2, ..., n-1 2025 2026 auto bw = fw.array.radial; 2027 2028 Array!(E, assignment, ordering, supportGC, size_t, less) ss0 = bw; // reversed 2029 static assert(is(Unqual!(ElementType!(typeof(ss0))) == E)); 2030 static assert(isInstanceOf!(Array, typeof(ss0))); 2031 assert(ss0.length == n); 2032 2033 static if (isOrdered!ordering) 2034 { 2035 if (!ss0.empty) { assert(ss0[0] == ss0[0]); } // trigger use of opindex 2036 assert(ss0[].equal(fw.array 2037 .sort!comp)); 2038 assert(ss0[].isSorted!comp); 2039 } 2040 2041 Array!(E, assignment, ordering, supportGC, size_t, less) ss1 = fw; // ordinary 2042 assert(ss1.length == n); 2043 2044 static if (isOrdered!ordering) 2045 { 2046 assert(ss1[].equal(fw.array 2047 .sort!comp)); 2048 assert(ss1[].isSorted!comp); 2049 } 2050 2051 Array!(E, assignment, ordering, supportGC, size_t, less) ss2 = fw.filter!(x => x & 1); 2052 assert(ss2.length == n/2); 2053 2054 static if (isOrdered!ordering) 2055 { 2056 assert(ss2[].equal(fw.filter!(x => x & 1) 2057 .array 2058 .sort!comp)); 2059 assert(ss2[].isSorted!comp); 2060 } 2061 2062 auto ssA = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 2063 static if (isOrdered!ordering) 2064 { 2065 static if (less == "a < b") 2066 { 2067 alias A = Array!(E, assignment, ordering, supportGC, size_t, less); 2068 const A x = [1, 2, 3, 4, 5, 6]; 2069 assert(x.front == 1); 2070 assert(x.back == 6); 2071 assert(x.lowerBound(3).equal([1, 2].s[])); 2072 assert(x.upperBound(3).equal([4, 5, 6].s[])); 2073 assert(x[].equal(x[])); // already sorted 2074 } 2075 2076 foreach (i; bw) 2077 { 2078 static if (ordering == Ordering.sortedUniqueSet) 2079 { 2080 assert(ssA.insertMany(i)[].equal([true].s[])); 2081 assert(ssA.insertMany(i)[].equal([false].s[])); 2082 } 2083 else 2084 { 2085 ssA.insertMany(i); 2086 } 2087 } 2088 assert(ssA[].equal(sort!comp(fw.array))); 2089 2090 auto ssB = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 2091 static if (ordering == Ordering.sortedUniqueSet) 2092 { 2093 assert(ssB.insertMany(1, 7, 4, 9)[].equal(true.repeat(4))); 2094 assert(ssB.insertMany(3, 6, 8, 5, 1, 9)[].equal([true, true, true, true, false, false].s[])); 2095 assert(ssB.insertMany(3, 0, 2, 10, 11, 5)[].equal([false, true, true, true, true, false].s[])); 2096 assert(ssB.insertMany(0, 2, 10, 11)[].equal(false.repeat(4))); // false becuse already inserted 2097 assert(ssB.capacity == 16); 2098 } 2099 else 2100 { 2101 ssB.insertMany(1, 7, 4, 9); 2102 ssB.insertMany(3, 6, 8, 5); 2103 ssB.insertMany(0, 2, 10, 11); 2104 assert(ssB.capacity == 16); 2105 } 2106 2107 auto ssI = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].sort!comp; // values 2108 immutable ssO = [12, 13]; // values not range 2109 2110 assert(ssB[].equal(ssI)); 2111 2112 foreach (s; ssI) { assert(ssB.contains(s)); } 2113 foreach (s; ssO) { assert(!ssB.contains(s)); } 2114 2115 ssB.compress(); 2116 assert(ssB.capacity == 12); 2117 } 2118 else 2119 { 2120 { 2121 alias A = Array!(E, assignment, ordering, supportGC); 2122 A x = [1, 2, 3]; 2123 x ~= x; 2124 assert(x[].equal([1, 2, 3, 2125 1, 2, 3].s[])); 2126 x ~= x[]; 2127 assert(x[].equal([1, 2, 3, 1, 2, 3, 2128 1, 2, 3, 1, 2, 3].s[])); 2129 } 2130 2131 ssA ~= 3; 2132 ssA ~= 2; 2133 ssA ~= 1; 2134 assert(ssA[].equal([3, 2, 1].s[])); 2135 2136 ssA.compress(); 2137 2138 // popBack 2139 ssA[0] = 1; 2140 ssA[1] = 2; 2141 assert(ssA[].equal([1, 2, 1].s[])); 2142 assert(!ssA.empty); 2143 assert(ssA.front == 1); 2144 assert(ssA.back == 1); 2145 2146 assertNotThrown(ssA.popBack()); 2147 assert(ssA[].equal([1, 2].s[])); 2148 assert(!ssA.empty); 2149 assert(ssA.front == 1); 2150 assert(ssA.back == 2); 2151 2152 assertNotThrown(ssA.popBack()); 2153 assert(ssA[].equal([1].s[])); 2154 assert(!ssA.empty); 2155 assert(ssA.front == 1); 2156 assert(ssA.back == 1); 2157 2158 assertNotThrown(ssA.popBack()); 2159 assert(ssA.length == 0); 2160 assert(ssA.empty); 2161 assert(ssA.capacity != 0); 2162 2163 ssA.compress(); 2164 assert(ssA.length == 0); 2165 assert(ssA.empty); 2166 2167 // insertAt 2168 ssA ~= 1; 2169 ssA ~= 2; 2170 ssA ~= 3; 2171 ssA ~= 4; 2172 ssA ~= 5; 2173 ssA ~= 6; 2174 ssA ~= 7; 2175 ssA ~= 8; 2176 assert(ssA[].equal([1, 2, 3, 4, 5, 6, 7, 8].s[])); 2177 ssA.insertAtIndex(3, 100, 101); 2178 assert(ssA[].equal([1, 2, 3, 100, 101, 4, 5, 6, 7, 8].s[])); 2179 assertNotThrown(ssA.popFront()); 2180 assert(ssA[].equal([2, 3, 100, 101, 4, 5, 6, 7, 8].s[])); 2181 assertNotThrown(ssA.popFront()); 2182 assert(ssA[].equal([3, 100, 101, 4, 5, 6, 7, 8].s[])); 2183 assertNotThrown(ssA.popFront()); 2184 assert(ssA[].equal([100, 101, 4, 5, 6, 7, 8].s[])); 2185 assertNotThrown(ssA.popFront()); 2186 assertNotThrown(ssA.popFront()); 2187 assertNotThrown(ssA.popFront()); 2188 assertNotThrown(ssA.popFront()); 2189 assertNotThrown(ssA.popFront()); 2190 assertNotThrown(ssA.popFront()); 2191 assertNotThrown(ssA.popFront()); 2192 assert(ssA.empty); 2193 ssA.compress(); 2194 2195 // removeAt 2196 ssA ~= 1; 2197 ssA ~= 2; 2198 ssA ~= 3; 2199 ssA ~= 4; 2200 ssA ~= 5; 2201 assertNotThrown(ssA.removeAt(2)); 2202 assert(ssA[].equal([1, 2, 4, 5].s[])); 2203 2204 // insertBack and assignment from slice 2205 auto ssB = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 2206 ssB.insertBack([1, 2, 3, 4, 5].s[]); 2207 ssB.insertBack([6, 7]); 2208 assert(ssB[].equal([1, 2, 3, 4, 5, 6, 7].s[])); 2209 assert(ssB.backPop() == 7); 2210 assert(ssB.backPop() == 6); 2211 assert(ssB.backPop() == 5); 2212 assert(ssB.backPop() == 4); 2213 assert(ssB.backPop() == 3); 2214 assert(ssB.backPop() == 2); 2215 assert(ssB.backPop() == 1); 2216 assert(ssB.empty); 2217 2218 // insertBack(Array) 2219 { 2220 immutable s = [1, 2, 3]; 2221 Array!(E, assignment, ordering, supportGC, size_t, less) s1 = s; 2222 Array!(E, assignment, ordering, supportGC, size_t, less) s2 = s1[]; 2223 assert(s1[].equal(s)); 2224 s1 ~= s1; 2225 assert(s1[].equal(chain(s, s))); 2226 s1 ~= s2; 2227 assert(s1[].equal(chain(s, s, s))); 2228 } 2229 2230 // immutable ss_ = Array!(E, assignment, ordering, supportGC, size_t, less)(null); 2231 // assert(ss_.empty); 2232 2233 auto ssC = Array!(E, assignment, ordering, supportGC, size_t, less).withLength(0); 2234 immutable int[5] i5_ = [1, 2, 3, 4, 5]; 2235 immutable(int)[] i5 = i5_[]; 2236 ssC.insertBack(i5); 2237 assert(i5 == [1, 2, 3, 4, 5].s[]); 2238 assert(ssC[].equal(i5)); 2239 2240 auto ssCc = ssC; // copy it 2241 assert(ssCc[].equal(i5)); 2242 2243 ssC.shrinkTo(4); 2244 assert(ssC[].equal([1, 2, 3, 4].s[])); 2245 2246 ssC.shrinkTo(3); 2247 assert(ssC[].equal([1, 2, 3].s[])); 2248 2249 ssC.shrinkTo(2); 2250 assert(ssC[].equal([1, 2].s[])); 2251 2252 ssC.shrinkTo(1); 2253 assert(ssC[].equal([1].s[])); 2254 2255 ssC.shrinkTo(0); 2256 assert(ssC[].length == 0); 2257 assert(ssC.empty); 2258 2259 ssC.insertBack(i5); 2260 ssC.popBackN(3); 2261 assert(ssC[].equal([1, 2].s[])); 2262 2263 auto ssD = ssC; 2264 ssC.clear(); 2265 assert(ssC.empty); 2266 2267 assert(!ssD.empty); 2268 ssD = null; 2269 assert(ssD.empty); 2270 assert(ssD == typeof(ssD).init); 2271 2272 assert(ssCc[].equal(i5)); 2273 2274 ssCc = ssCc; // self assignment 2275 } 2276 } 2277 } 2278 2279 /// disabled copying 2280 @safe pure nothrow @nogc unittest 2281 { 2282 alias E = ubyte; 2283 alias A = Array!(E, Assignment.disabled, Ordering.unsorted, false, size_t, "a < b"); 2284 A a; 2285 immutable n = ubyte.max; 2286 size_t i = 0; 2287 foreach (ubyte e; 0 .. n) 2288 { 2289 a ~= e; 2290 // assert(a.back == e.to!E); 2291 assert(a.length == i + 1); 2292 ++i; 2293 } 2294 const b = a.dup; 2295 static assert(is(typeof(a) == Unqual!(typeof(b)))); 2296 assert(b.length == a.length); 2297 assert(a !is b); 2298 assert(a == b); 2299 } 2300 2301 /// disabled copying 2302 @safe pure nothrow unittest 2303 { 2304 alias E = string; 2305 2306 alias A = Array!(E, Assignment.disabled, Ordering.unsorted, false, size_t, "a < b"); 2307 A a; 2308 immutable n = 100_000; 2309 size_t i = 0; 2310 foreach (const ref e; 0 .. n) 2311 { 2312 a ~= e.to!E; 2313 // assert(a.back == e.to!E); 2314 assert(a.length == i + 1); 2315 ++i; 2316 } 2317 const b = a.dup; 2318 static assert(is(typeof(a) == Unqual!(typeof(b)))); 2319 assert(b.length == a.length); 2320 assert(a !is b); 2321 assert(a == b); 2322 } 2323 2324 /// disabled copying 2325 @safe pure nothrow unittest 2326 { 2327 alias E = string; 2328 alias A = Array!(E, Assignment.disabled, Ordering.unsorted, false, size_t, "a < b"); 2329 A a; 2330 immutable n = 100_000; 2331 size_t i = 0; 2332 foreach (const ref e; 0 .. n) 2333 { 2334 a ~= e.to!E; 2335 assert(a.length == i + 1); 2336 ++i; 2337 } 2338 const b = a.dup; 2339 static assert(is(typeof(a) == Unqual!(typeof(b)))); 2340 assert(a[] == b[]); 2341 } 2342 2343 /// disabled copying 2344 @safe pure nothrow @nogc unittest 2345 { 2346 import std.traits : isAssignable; 2347 2348 alias E = string; 2349 2350 alias A = Array!E; 2351 static assert(!__traits(isCopyable, A)); 2352 2353 alias CA = CopyingArray!E; 2354 static assert(__traits(isCopyable, CA)); 2355 2356 // import std.traits : isRvalueAssignable, isLvalueAssignable; 2357 // static assert(isRvalueAssignable!(A)); 2358 // static assert(isLvalueAssignable!(A)); 2359 2360 static assert(isAssignable!(A)); 2361 2362 // import std.range.primitives : hasSlicing; 2363 // TODO make this evaluate to `true` 2364 // static assert(hasSlicing!A); 2365 2366 alias AA = Array!A; 2367 2368 AA aa; 2369 A a; 2370 a ~= "string"; 2371 aa ~= A.init; 2372 2373 assert(aa == aa); 2374 assert(AA.withLength(3) == AA.withLength(3)); 2375 assert(AA.withCapacity(3) == AA.withCapacity(3)); 2376 assert(AA.withLength(3).length == 3); 2377 assert(aa != AA.init); 2378 } 2379 2380 /// 2381 @safe pure nothrow @nogc unittest 2382 { 2383 alias E = int; 2384 alias A = Array!E; 2385 A a; 2386 import std.range : iota; 2387 import std.container.util : make; 2388 foreach (n; 0 .. 100) 2389 { 2390 const e = iota(0, n).make!Array; 2391 assert(e[].equal(iota(0, n))); 2392 } 2393 } 2394 2395 version(unittest) 2396 { 2397 import std.traits : EnumMembers; 2398 } 2399 2400 /// use GC 2401 pure nothrow unittest 2402 { 2403 foreach (ordering; EnumMembers!Ordering) 2404 { 2405 tester!(ordering, true, "a < b"); // use GC 2406 tester!(ordering, true, "a > b"); // use GC 2407 } 2408 } 2409 2410 /// don't use GC 2411 pure nothrow /+TODO @nogc+/ unittest 2412 { 2413 foreach (ordering; EnumMembers!Ordering) 2414 { 2415 tester!(ordering, false, "a < b"); // don't use GC 2416 tester!(ordering, false, "a > b"); // don't use GC 2417 } 2418 } 2419 2420 @safe pure nothrow unittest 2421 { 2422 alias E = int; 2423 alias A = Array!E; 2424 A[string] map; 2425 map["a"] = A.init; 2426 map["B"] = A.withLength(42); 2427 2428 auto aPtr = "a" in map; 2429 assert(aPtr); 2430 assert(A.init == *aPtr); 2431 assert(*aPtr == A.init); 2432 2433 assert("z" !in map); 2434 auto zPtr = "z" in map; 2435 assert(!zPtr); 2436 } 2437 2438 /// test withElement and withElements 2439 @safe pure nothrow @nogc unittest 2440 { 2441 import std.algorithm.mutation : move; 2442 import std.range.primitives : ElementType; 2443 2444 alias A = Array!int; 2445 alias AA = Array!A; 2446 alias AAA = Array!AA; 2447 2448 foreach (A_; AliasSeq!(A, AA, AAA)) 2449 { 2450 alias E = ElementType!A_; 2451 A_ x = A_.withElement(E.init); 2452 A_ y = A_.withElements(E.init, E.init); 2453 assert(x.length == 1); 2454 assert(y.length == 2); 2455 immutable n = 100; 2456 foreach (_; 0 .. n) 2457 { 2458 auto e = E.init; 2459 x ~= move(e); 2460 y ~= E.init; 2461 } 2462 foreach (_; 0 .. n) 2463 { 2464 assert(x.backPop() == E.init); 2465 assert(y.backPop() == E.init); 2466 } 2467 assert(x.length == 1); 2468 assert(y.length == 2); 2469 2470 import std.algorithm : swap; 2471 swap(x, y); 2472 assert(x.length == 2); 2473 assert(y.length == 1); 2474 2475 swap(x[0], y[0]); 2476 } 2477 2478 } 2479 2480 /// assert same behaviour of `dup` as for builtin arrays 2481 @safe pure nothrow unittest 2482 { 2483 struct Vec { int x, y; } 2484 class Db { int* _ptr; } 2485 struct Node { int x; class Db; } 2486 // struct Node1 { const(int) x; class Db; } 2487 foreach (E; AliasSeq!(int, const(int), Vec, Node// , Node1 2488 )) 2489 { 2490 alias DA = E[]; // builtin D array/slice 2491 immutable DA da = [E.init]; // construct from array 2492 auto daCopy = da.dup; // duplicate 2493 daCopy[] = E.init; // opSliceAssign 2494 2495 alias CA = Array!E; // container array 2496 immutable ca = CA.withElement(E.init); 2497 2498 auto caCopy = ca.dup; 2499 2500 import std.traits : hasIndirections; 2501 static if (!hasIndirections!E) 2502 { 2503 const(E)[2] x = [E.init, E.init]; 2504 // TODO caCopy ~= E.init; 2505 caCopy ~= x[]; 2506 assert(caCopy.length == 3); 2507 assert(caCopy[1 .. $] == x[]); 2508 } 2509 2510 // should have same element type 2511 static assert(is(typeof(caCopy[0]) == 2512 typeof(daCopy[0]))); 2513 2514 } 2515 } 2516 2517 /// array as AA key type 2518 @safe pure nothrow unittest 2519 { 2520 struct E { int x, y; } 2521 foreach (A; AliasSeq!(Array!E, 2522 CopyingArray!E)) 2523 { 2524 int[A] x; 2525 immutable n = 100; 2526 foreach (immutable i; 0 .. n) 2527 { 2528 assert(x.length == i); 2529 assert(A.withElement(E(i, 2*i)) !in x); 2530 x[A.withElement(E(i, 2*i))] = 42; 2531 assert(x.length == i + 1); 2532 auto a = A.withElement(E(i, 2*i)); 2533 import std.traits : isCopyable; 2534 static if (__traits(isCopyable, A)) 2535 { 2536 // TODO why do these fail when `A` is uncopyable? 2537 assert(a in x); 2538 assert(A.withElement(E(i, 2*i)) in x); 2539 assert(x[A.withElement(E(i, 2*i))] == 42); 2540 } 2541 } 2542 } 2543 } 2544 2545 /// init and append to empty array as AA value type 2546 @safe pure nothrow unittest 2547 { 2548 alias Key = string; 2549 alias A = Array!int; 2550 2551 A[Key] x; 2552 2553 assert("a" !in x); 2554 2555 x["a"] = A.init; // if this init is removed.. 2556 x["a"] ~= 42; // ..then this fails 2557 2558 assert(x["a"] == A.withElement(42)); 2559 } 2560 2561 /// compress 2562 @safe pure nothrow @nogc unittest 2563 { 2564 alias A = Array!string; 2565 A a; 2566 2567 a.compress(); 2568 2569 a ~= "a"; 2570 a ~= "b"; 2571 a ~= "c"; 2572 2573 assert(a.length == 3); 2574 assert(a.capacity == 4); 2575 2576 a.compress(); 2577 2578 assert(a.capacity == a.length); 2579 } 2580 2581 /// 2582 @safe pure nothrow @nogc unittest 2583 { 2584 alias Key = UniqueArray!char; 2585 alias Value = UniqueArray!int; 2586 struct E 2587 { 2588 Key key; 2589 Value value; 2590 E dup() @safe pure nothrow @nogc 2591 { 2592 return E(key.dup, value.dup); 2593 } 2594 } 2595 E e; 2596 e.key = Key.withElement('a'); 2597 e.value = Value.withElement(42); 2598 2599 auto f = e.dup; 2600 assert(e == f); 2601 2602 e.key = Key.withElement('b'); 2603 assert(e != f); 2604 2605 e.key = Key.withElement('a'); 2606 assert(e == f); 2607 2608 e.value = Value.withElement(43); 2609 assert(e != f); 2610 2611 e.value = Value.withElement(42); 2612 assert(e == f); 2613 2614 } 2615 2616 /// append to empty to array as AA value type 2617 @safe pure nothrow @nogc unittest 2618 { 2619 import std.exception: assertThrown; 2620 import core.exception : RangeError; 2621 2622 alias Key = string; 2623 alias A = Array!int; 2624 2625 A[Key] x; 2626 // assertThrown!RangeError({ x["a"] ~= 42; }); // TODO make this work 2627 } 2628 2629 /// map array of uncopyable 2630 @safe pure nothrow unittest 2631 { 2632 import std.range.primitives : isInputRange; 2633 import std.array : array; 2634 2635 alias A = UniqueArray!int; 2636 auto y = A.init[].map!(_ => _^^2).array; 2637 2638 A z = y.dup; // check that dup returns same type 2639 z = A.init; 2640 const w = [0, 1].s; 2641 z.insertBack(w[]); 2642 assert(z[].equal(w[])); 2643 } 2644 2645 /// 2646 version(none) 2647 @safe pure nothrow @nogc unittest 2648 { 2649 alias A = UniqueArray!int; 2650 A x; 2651 const y = [0, 1, 2, 3].s; 2652 2653 x.insertBack(y[]); 2654 assert(x[].equal(y[])); 2655 2656 x.clear(); 2657 x.insertBack(y[].map!(_ => _^^2)); // rhs has length (`hasLength`) 2658 assert(x[].equal(y[].map!(_ => _^^2))); 2659 2660 x.clear(); 2661 x.insertBack(y[].filter!(_ => _ & 1)); // rhs has no length (`!hasLength`) 2662 assert(x[].equal(y[].filter!(_ => _ & 1))); 2663 } 2664 2665 /// collection 2666 /*@safe*/ pure nothrow @nogc unittest // TODO make @safe when collect has been made safe 2667 { 2668 import std.range : iota, isOutputRange; 2669 import nxt.algorithm_ex : collect; 2670 2671 alias E = int; 2672 alias A = Array!E; 2673 2674 immutable n = 100; 2675 static assert(isOutputRange!(A, E)); 2676 2677 assert((0.iota(n).collect!A)[].equal(0.iota(n))); 2678 } 2679 2680 /// map array of uncopyable 2681 @safe pure nothrow @nogc unittest 2682 { 2683 foreach (AT; AliasSeq!(SortedUniqueArray, 2684 SortedSetUniqueArray)) 2685 { 2686 alias A = AT!int; 2687 A a; 2688 A b = a.dup; 2689 } 2690 } 2691 2692 /// init and append to empty array as AA value type 2693 @safe pure nothrow @nogc unittest 2694 { 2695 alias A = Array!int; 2696 2697 const x = A.withElements(0, 1, 3, 0, 2, 1, 3); 2698 2699 assert(x.toSortedArray == [0, 0, 1, 1, 2, 3, 3].s[]); 2700 assert(x.toSortedSetArray == [0, 1, 2, 3].s[]); 2701 2702 assert(x.toSortedArray!"a > b" == [3, 3, 2, 1, 1, 0, 0].s[]); 2703 assert(x.toSortedSetArray!"a > b" == [3, 2, 1, 0].s[]); 2704 } 2705 2706 /** Return `data` appended with arguments `args`. 2707 2708 If `data` is an r-value it's modified and returned, otherwise a copy is made 2709 and returned. 2710 */ 2711 C append(C, Args...)(auto ref C data, 2712 auto ref Args args) 2713 if (args.length >= 1) // TODO trait: when `C` is a container supporting `insertBack` 2714 { 2715 static if (__traits(isRef, data)) // `data` is an r-value 2716 { 2717 C mutableData = data.dup; 2718 } 2719 else // `data` is an l-value 2720 { 2721 alias mutableData = data; 2722 } 2723 // TODO use `mutableData.insertBack(args);` instead 2724 foreach (ref arg; args) 2725 { 2726 mutableData.insertBack(arg); 2727 } 2728 import std.algorithm.mutation : move; 2729 return move(mutableData); 2730 } 2731 2732 /// append 2733 @safe pure nothrow @nogc unittest 2734 { 2735 alias Str = UniqueString!false; 2736 2737 assert(Str(`a`).append('b', 'c')[] == `abc`); 2738 assert(Str(`a`).append(`b`, `c`)[] == `abc`); 2739 2740 const Str x = Str(`a`).append('b', 'c'); // is moved 2741 assert(x[] == `abc`); 2742 2743 Str y = `x`; 2744 Str z = y.append('y', 'z', `w`); // needs dup 2745 assert(y.ptr != z.ptr); 2746 assert(z[] == `xyzw`); 2747 } 2748 2749 version(unittest) 2750 { 2751 private static struct SS 2752 { 2753 @disable this(this); 2754 int x; 2755 } 2756 } 2757 2758 /// uncopyable elements 2759 @safe pure nothrow @nogc unittest 2760 { 2761 alias A = UniqueArray!SS; 2762 A x; 2763 x ~= SS.init; 2764 // TODO x.insertBack(A.init); 2765 } 2766 2767 // TODO implement? 2768 // T opBinary(string op, R, Args...)(R lhs, 2769 // auto ref Args args) 2770 // { 2771 // return append(lhs, rhs); 2772 // } 2773 // @safe pure nothrow @nogc unittest 2774 // { 2775 // alias S = UniqueString!false; 2776 // // TODO 2777 // // const S x = S(`a`) ~ 'b'; 2778 // // assert(x[] == `abc`); 2779 // } 2780 2781 /// See_Also: http://forum.dlang.org/post/omfm56$28nu$1@digitalmars.com 2782 version(none) 2783 @safe pure nothrow unittest 2784 { 2785 import std.range.primitives : ElementType; 2786 import std.array : Appender; 2787 2788 struct S 2789 { 2790 string src; 2791 S[] subs; 2792 } 2793 2794 struct T 2795 { 2796 string src; 2797 Appender!(T[]) subs; 2798 } 2799 2800 static assert(is(ElementType!(S[]) == S)); 2801 static assert(is(ElementType!(T[]) == void)); // TODO forward-reference bug: should be `T` 2802 2803 S s; 2804 s.subs ~= S.init; 2805 2806 T t; 2807 // t.subs ~= T.init; 2808 // t.subs.put(T.init); 2809 2810 // struct U 2811 // { 2812 // string src; 2813 // UniqueArray!U subs; 2814 // } 2815 // U u; 2816 } 2817 2818 /// class element 2819 @safe pure nothrow unittest 2820 { 2821 class Zing 2822 { 2823 void* raw; 2824 } 2825 class Edge : Zing 2826 { 2827 Zing[] actors; 2828 } 2829 2830 foreach (AT; AliasSeq!(UniqueArray, 2831 CopyingArray, 2832 SortedCopyingArray, 2833 SortedSetCopyingArray, 2834 SortedUniqueArray, 2835 SortedSetUniqueArray)) 2836 { 2837 alias A = AT!int; 2838 A a; 2839 } 2840 }