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