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