| /** | |
| * This module provides an `Array` type with deterministic memory usage not | |
| * reliant on the GC, as an alternative to the built-in arrays. | |
| * | |
| * This module is a submodule of $(MREF std, container). | |
| * | |
| * Source: $(PHOBOSSRC std/container/_array.d) | |
| * | |
| * Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. | |
| * | |
| * License: Distributed under the Boost Software License, Version 1.0. | |
| * (See accompanying file LICENSE_1_0.txt or copy at $(HTTP | |
| * boost.org/LICENSE_1_0.txt)). | |
| * | |
| * Authors: $(HTTP erdani.com, Andrei Alexandrescu) | |
| * | |
| * $(SCRIPT inhibitQuickIndex = 1;) | |
| */ | |
| module std.container.array; | |
| import core.exception : RangeError; | |
| import std.range.primitives; | |
| import std.traits; | |
| public import std.container.util; | |
| /// | |
| @system unittest | |
| { | |
| auto arr = Array!int(0, 2, 3); | |
| assert(arr[0] == 0); | |
| assert(arr.front == 0); | |
| assert(arr.back == 3); | |
| // reserve space | |
| arr.reserve(1000); | |
| assert(arr.length == 3); | |
| assert(arr.capacity >= 1000); | |
| // insertion | |
| arr.insertBefore(arr[1..$], 1); | |
| assert(arr.front == 0); | |
| assert(arr.length == 4); | |
| arr.insertBack(4); | |
| assert(arr.back == 4); | |
| assert(arr.length == 5); | |
| // set elements | |
| arr[1] *= 42; | |
| assert(arr[1] == 42); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto arr = Array!int(1, 2, 3); | |
| // concat | |
| auto b = Array!int(11, 12, 13); | |
| arr ~= b; | |
| assert(arr.length == 6); | |
| // slicing | |
| assert(arr[1 .. 3].equal([2, 3])); | |
| // remove | |
| arr.linearRemove(arr[1 .. 3]); | |
| assert(arr[0 .. 2].equal([1, 11])); | |
| } | |
| /// `Array!bool` packs together values efficiently by allocating one bit per element | |
| @system unittest | |
| { | |
| Array!bool arr; | |
| arr.insert([true, true, false, true, false]); | |
| assert(arr.length == 5); | |
| } | |
| private struct RangeT(A) | |
| { | |
| /* Workaround for Issue 13629 at https://issues.dlang.org/show_bug.cgi?id=13629 | |
| See also: http://forum.dlang.org/post/vbmwhzvawhnkoxrhbnyb@forum.dlang.org | |
| */ | |
| private A[1] _outer_; | |
| private @property ref inout(A) _outer() inout { return _outer_[0]; } | |
| private size_t _a, _b; | |
| /* E is different from T when A is more restrictively qualified than T: | |
| immutable(Array!int) => T == int, E = immutable(int) */ | |
| alias E = typeof(_outer_[0]._data._payload[0]); | |
| private this(ref A data, size_t a, size_t b) | |
| { | |
| _outer_ = data; | |
| _a = a; | |
| _b = b; | |
| } | |
| @property RangeT save() | |
| { | |
| return this; | |
| } | |
| @property bool empty() @safe pure nothrow const | |
| { | |
| return _a >= _b; | |
| } | |
| @property size_t length() @safe pure nothrow const | |
| { | |
| return _b - _a; | |
| } | |
| alias opDollar = length; | |
| @property ref inout(E) front() inout | |
| { | |
| assert(!empty, "Attempting to access the front of an empty Array"); | |
| return _outer[_a]; | |
| } | |
| @property ref inout(E) back() inout | |
| { | |
| assert(!empty, "Attempting to access the back of an empty Array"); | |
| return _outer[_b - 1]; | |
| } | |
| void popFront() @safe @nogc pure nothrow | |
| { | |
| assert(!empty, "Attempting to popFront an empty Array"); | |
| ++_a; | |
| } | |
| void popBack() @safe @nogc pure nothrow | |
| { | |
| assert(!empty, "Attempting to popBack an empty Array"); | |
| --_b; | |
| } | |
| static if (isMutable!A) | |
| { | |
| import std.algorithm.mutation : move; | |
| E moveFront() | |
| { | |
| assert(!empty && _a < _outer.length); | |
| return move(_outer._data._payload[_a]); | |
| } | |
| E moveBack() | |
| { | |
| assert(!empty && _b <= _outer.length); | |
| return move(_outer._data._payload[_b - 1]); | |
| } | |
| E moveAt(size_t i) | |
| { | |
| assert(_a + i < _b && _a + i < _outer.length); | |
| return move(_outer._data._payload[_a + i]); | |
| } | |
| } | |
| ref inout(E) opIndex(size_t i) inout | |
| { | |
| assert(_a + i < _b); | |
| return _outer[_a + i]; | |
| } | |
| RangeT opSlice() | |
| { | |
| return typeof(return)(_outer, _a, _b); | |
| } | |
| RangeT opSlice(size_t i, size_t j) | |
| { | |
| assert(i <= j && _a + j <= _b); | |
| return typeof(return)(_outer, _a + i, _a + j); | |
| } | |
| RangeT!(const(A)) opSlice() const | |
| { | |
| return typeof(return)(_outer, _a, _b); | |
| } | |
| RangeT!(const(A)) opSlice(size_t i, size_t j) const | |
| { | |
| assert(i <= j && _a + j <= _b); | |
| return typeof(return)(_outer, _a + i, _a + j); | |
| } | |
| static if (isMutable!A) | |
| { | |
| void opSliceAssign(E value) | |
| { | |
| assert(_b <= _outer.length); | |
| _outer[_a .. _b] = value; | |
| } | |
| void opSliceAssign(E value, size_t i, size_t j) | |
| { | |
| assert(_a + j <= _b); | |
| _outer[_a + i .. _a + j] = value; | |
| } | |
| void opSliceUnary(string op)() | |
| if (op == "++" || op == "--") | |
| { | |
| assert(_b <= _outer.length); | |
| mixin(op~"_outer[_a .. _b];"); | |
| } | |
| void opSliceUnary(string op)(size_t i, size_t j) | |
| if (op == "++" || op == "--") | |
| { | |
| assert(_a + j <= _b); | |
| mixin(op~"_outer[_a + i .. _a + j];"); | |
| } | |
| void opSliceOpAssign(string op)(E value) | |
| { | |
| assert(_b <= _outer.length); | |
| mixin("_outer[_a .. _b] "~op~"= value;"); | |
| } | |
| void opSliceOpAssign(string op)(E value, size_t i, size_t j) | |
| { | |
| assert(_a + j <= _b); | |
| mixin("_outer[_a + i .. _a + j] "~op~"= value;"); | |
| } | |
| } | |
| } | |
| /** | |
| * _Array type with deterministic control of memory. The memory allocated | |
| * for the array is reclaimed as soon as possible; there is no reliance | |
| * on the garbage collector. `Array` uses `malloc`, `realloc` and `free` | |
| * for managing its own memory. | |
| * | |
| * This means that pointers to elements of an `Array` will become | |
| * dangling as soon as the element is removed from the `Array`. On the other hand | |
| * the memory allocated by an `Array` will be scanned by the GC and | |
| * GC managed objects referenced from an `Array` will be kept alive. | |
| * | |
| * Note: | |
| * | |
| * When using `Array` with range-based functions like those in `std.algorithm`, | |
| * `Array` must be sliced to get a range (for example, use `array[].map!` | |
| * instead of `array.map!`). The container itself is not a range. | |
| */ | |
| struct Array(T) | |
| if (!is(Unqual!T == bool)) | |
| { | |
| import core.stdc.stdlib : malloc, realloc, free; | |
| import core.stdc.string : memcpy, memmove, memset; | |
| import core.memory : GC; | |
| import std.exception : enforce; | |
| import std.typecons : RefCounted, RefCountedAutoInitialize; | |
| // This structure is not copyable. | |
| private struct Payload | |
| { | |
| size_t _capacity; | |
| T[] _payload; | |
| this(T[] p) { _capacity = p.length; _payload = p; } | |
| // Destructor releases array memory | |
| ~this() | |
| { | |
| // Warning: destroy would destroy also class instances. | |
| // The hasElaborateDestructor protects us here. | |
| static if (hasElaborateDestructor!T) | |
| foreach (ref e; _payload) | |
| .destroy(e); | |
| static if (hasIndirections!T) | |
| GC.removeRange(_payload.ptr); | |
| free(_payload.ptr); | |
| } | |
| this(this) @disable; | |
| void opAssign(Payload rhs) @disable; | |
| @property size_t length() const | |
| { | |
| return _payload.length; | |
| } | |
| @property void length(size_t newLength) | |
| { | |
| import std.algorithm.mutation : initializeAll; | |
| if (length >= newLength) | |
| { | |
| // shorten | |
| static if (hasElaborateDestructor!T) | |
| foreach (ref e; _payload.ptr[newLength .. _payload.length]) | |
| .destroy(e); | |
| _payload = _payload.ptr[0 .. newLength]; | |
| return; | |
| } | |
| immutable startEmplace = length; | |
| if (_capacity < newLength) | |
| { | |
| // enlarge | |
| import core.checkedint : mulu; | |
| bool overflow; | |
| const nbytes = mulu(newLength, T.sizeof, overflow); | |
| if (overflow) | |
| assert(0); | |
| _payload = (cast(T*) realloc(_payload.ptr, nbytes))[0 .. newLength]; | |
| _capacity = newLength; | |
| } | |
| else | |
| { | |
| _payload = _payload.ptr[0 .. newLength]; | |
| } | |
| initializeAll(_payload.ptr[startEmplace .. newLength]); | |
| } | |
| @property size_t capacity() const | |
| { | |
| return _capacity; | |
| } | |
| void reserve(size_t elements) | |
| { | |
| if (elements <= capacity) return; | |
| import core.checkedint : mulu; | |
| bool overflow; | |
| const sz = mulu(elements, T.sizeof, overflow); | |
| if (overflow) | |
| assert(0); | |
| static if (hasIndirections!T) | |
| { | |
| /* Because of the transactional nature of this | |
| * relative to the garbage collector, ensure no | |
| * threading bugs by using malloc/copy/free rather | |
| * than realloc. | |
| */ | |
| immutable oldLength = length; | |
| auto newPayloadPtr = cast(T*) malloc(sz); | |
| newPayloadPtr || assert(false, "std.container.Array.reserve failed to allocate memory"); | |
| auto newPayload = newPayloadPtr[0 .. oldLength]; | |
| // copy old data over to new array | |
| memcpy(newPayload.ptr, _payload.ptr, T.sizeof * oldLength); | |
| // Zero out unused capacity to prevent gc from seeing false pointers | |
| memset(newPayload.ptr + oldLength, | |
| 0, | |
| (elements - oldLength) * T.sizeof); | |
| GC.addRange(newPayload.ptr, sz); | |
| GC.removeRange(_payload.ptr); | |
| free(_payload.ptr); | |
| _payload = newPayload; | |
| } | |
| else | |
| { | |
| // These can't have pointers, so no need to zero unused region | |
| auto newPayloadPtr = cast(T*) realloc(_payload.ptr, sz); | |
| newPayloadPtr || assert(false, "std.container.Array.reserve failed to allocate memory"); | |
| auto newPayload = newPayloadPtr[0 .. length]; | |
| _payload = newPayload; | |
| } | |
| _capacity = elements; | |
| } | |
| // Insert one item | |
| size_t insertBack(Elem)(Elem elem) | |
| if (isImplicitlyConvertible!(Elem, T)) | |
| { | |
| import std.conv : emplace; | |
| if (_capacity == length) | |
| { | |
| reserve(1 + capacity * 3 / 2); | |
| } | |
| assert(capacity > length && _payload.ptr); | |
| emplace(_payload.ptr + _payload.length, elem); | |
| _payload = _payload.ptr[0 .. _payload.length + 1]; | |
| return 1; | |
| } | |
| // Insert a range of items | |
| size_t insertBack(Range)(Range r) | |
| if (isInputRange!Range && isImplicitlyConvertible!(ElementType!Range, T)) | |
| { | |
| static if (hasLength!Range) | |
| { | |
| immutable oldLength = length; | |
| reserve(oldLength + r.length); | |
| } | |
| size_t result; | |
| foreach (item; r) | |
| { | |
| insertBack(item); | |
| ++result; | |
| } | |
| static if (hasLength!Range) | |
| { | |
| assert(length == oldLength + r.length); | |
| } | |
| return result; | |
| } | |
| } | |
| private alias Data = RefCounted!(Payload, RefCountedAutoInitialize.no); | |
| private Data _data; | |
| /** | |
| * Constructor taking a number of items. | |
| */ | |
| this(U)(U[] values...) | |
| if (isImplicitlyConvertible!(U, T)) | |
| { | |
| import core.checkedint : mulu; | |
| import std.conv : emplace; | |
| bool overflow; | |
| const nbytes = mulu(values.length, T.sizeof, overflow); | |
| if (overflow) assert(0); | |
| auto p = cast(T*) malloc(nbytes); | |
| static if (hasIndirections!T) | |
| { | |
| if (p) | |
| GC.addRange(p, T.sizeof * values.length); | |
| } | |
| foreach (i, e; values) | |
| { | |
| emplace(p + i, e); | |
| } | |
| _data = Data(p[0 .. values.length]); | |
| } | |
| /** | |
| * Constructor taking an input range | |
| */ | |
| this(Range)(Range r) | |
| if (isInputRange!Range && isImplicitlyConvertible!(ElementType!Range, T) && !is(Range == T[])) | |
| { | |
| insertBack(r); | |
| } | |
| /** | |
| * Comparison for equality. | |
| */ | |
| bool opEquals(const Array rhs) const | |
| { | |
| return opEquals(rhs); | |
| } | |
| /// ditto | |
| bool opEquals(ref const Array rhs) const | |
| { | |
| if (empty) return rhs.empty; | |
| if (rhs.empty) return false; | |
| return _data._payload == rhs._data._payload; | |
| } | |
| /** | |
| * Defines the array's primary range, which is a random-access range. | |
| * | |
| * `ConstRange` is a variant with `const` elements. | |
| * `ImmutableRange` is a variant with `immutable` elements. | |
| */ | |
| alias Range = RangeT!Array; | |
| /// ditto | |
| alias ConstRange = RangeT!(const Array); | |
| /// ditto | |
| alias ImmutableRange = RangeT!(immutable Array); | |
| /** | |
| * Duplicates the array. The elements themselves are not transitively | |
| * duplicated. | |
| * | |
| * Complexity: $(BIGOH length). | |
| */ | |
| @property Array dup() | |
| { | |
| if (!_data.refCountedStore.isInitialized) return this; | |
| return Array(_data._payload); | |
| } | |
| /** | |
| * Returns: `true` if and only if the array has no elements. | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| @property bool empty() const | |
| { | |
| return !_data.refCountedStore.isInitialized || _data._payload.empty; | |
| } | |
| /** | |
| * Returns: The number of elements in the array. | |
| * | |
| * Complexity: $(BIGOH 1). | |
| */ | |
| @property size_t length() const | |
| { | |
| return _data.refCountedStore.isInitialized ? _data._payload.length : 0; | |
| } | |
| /// ditto | |
| size_t opDollar() const | |
| { | |
| return length; | |
| } | |
| /** | |
| * Returns: The maximum number of elements the array can store without | |
| * reallocating memory and invalidating iterators upon insertion. | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| @property size_t capacity() | |
| { | |
| return _data.refCountedStore.isInitialized ? _data._capacity : 0; | |
| } | |
| /** | |
| * Ensures sufficient capacity to accommodate `e` _elements. | |
| * If `e < capacity`, this method does nothing. | |
| * | |
| * Postcondition: `capacity >= e` | |
| * | |
| * Note: If the capacity is increased, one should assume that all | |
| * iterators to the elements are invalidated. | |
| * | |
| * Complexity: at most $(BIGOH length) if `e > capacity`, otherwise $(BIGOH 1). | |
| */ | |
| void reserve(size_t elements) | |
| { | |
| if (!_data.refCountedStore.isInitialized) | |
| { | |
| if (!elements) return; | |
| import core.checkedint : mulu; | |
| bool overflow; | |
| const sz = mulu(elements, T.sizeof, overflow); | |
| if (overflow) assert(0); | |
| auto p = malloc(sz); | |
| p || assert(false, "std.container.Array.reserve failed to allocate memory"); | |
| static if (hasIndirections!T) | |
| { | |
| GC.addRange(p, sz); | |
| } | |
| _data = Data(cast(T[]) p[0 .. 0]); | |
| _data._capacity = elements; | |
| } | |
| else | |
| { | |
| _data.reserve(elements); | |
| } | |
| } | |
| /** | |
| * Returns: A range that iterates over elements of the array in | |
| * forward order. | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| Range opSlice() | |
| { | |
| return typeof(return)(this, 0, length); | |
| } | |
| ConstRange opSlice() const | |
| { | |
| return typeof(return)(this, 0, length); | |
| } | |
| ImmutableRange opSlice() immutable | |
| { | |
| return typeof(return)(this, 0, length); | |
| } | |
| /** | |
| * Returns: A range that iterates over elements of the array from | |
| * index `i` up to (excluding) index `j`. | |
| * | |
| * Precondition: `i <= j && j <= length` | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| Range opSlice(size_t i, size_t j) | |
| { | |
| assert(i <= j && j <= length); | |
| return typeof(return)(this, i, j); | |
| } | |
| ConstRange opSlice(size_t i, size_t j) const | |
| { | |
| assert(i <= j && j <= length); | |
| return typeof(return)(this, i, j); | |
| } | |
| ImmutableRange opSlice(size_t i, size_t j) immutable | |
| { | |
| assert(i <= j && j <= length); | |
| return typeof(return)(this, i, j); | |
| } | |
| /** | |
| * Returns: The first element of the array. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| @property ref inout(T) front() inout | |
| { | |
| assert(_data.refCountedStore.isInitialized); | |
| return _data._payload[0]; | |
| } | |
| /** | |
| * Returns: The last element of the array. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| @property ref inout(T) back() inout | |
| { | |
| assert(_data.refCountedStore.isInitialized); | |
| return _data._payload[$ - 1]; | |
| } | |
| /** | |
| * Returns: The element or a reference to the element at the specified index. | |
| * | |
| * Precondition: `i < length` | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| ref inout(T) opIndex(size_t i) inout | |
| { | |
| assert(_data.refCountedStore.isInitialized); | |
| return _data._payload[i]; | |
| } | |
| /** | |
| * Slicing operators executing the specified operation on the entire slice. | |
| * | |
| * Precondition: `i < j && j < length` | |
| * | |
| * Complexity: $(BIGOH slice.length) | |
| */ | |
| void opSliceAssign(T value) | |
| { | |
| if (!_data.refCountedStore.isInitialized) return; | |
| _data._payload[] = value; | |
| } | |
| /// ditto | |
| void opSliceAssign(T value, size_t i, size_t j) | |
| { | |
| auto slice = _data.refCountedStore.isInitialized ? | |
| _data._payload : | |
| T[].init; | |
| slice[i .. j] = value; | |
| } | |
| /// ditto | |
| void opSliceUnary(string op)() | |
| if (op == "++" || op == "--") | |
| { | |
| if (!_data.refCountedStore.isInitialized) return; | |
| mixin(op~"_data._payload[];"); | |
| } | |
| /// ditto | |
| void opSliceUnary(string op)(size_t i, size_t j) | |
| if (op == "++" || op == "--") | |
| { | |
| auto slice = _data.refCountedStore.isInitialized ? _data._payload : T[].init; | |
| mixin(op~"slice[i .. j];"); | |
| } | |
| /// ditto | |
| void opSliceOpAssign(string op)(T value) | |
| { | |
| if (!_data.refCountedStore.isInitialized) return; | |
| mixin("_data._payload[] "~op~"= value;"); | |
| } | |
| /// ditto | |
| void opSliceOpAssign(string op)(T value, size_t i, size_t j) | |
| { | |
| auto slice = _data.refCountedStore.isInitialized ? _data._payload : T[].init; | |
| mixin("slice[i .. j] "~op~"= value;"); | |
| } | |
| private enum hasSliceWithLength(T) = is(typeof({ T t = T.init; t[].length; })); | |
| /** | |
| * Returns: A new array which is a concatenation of `this` and its argument. | |
| * | |
| * Complexity: | |
| * $(BIGOH length + m), where `m` is the number of elements in `stuff`. | |
| */ | |
| Array opBinary(string op, Stuff)(Stuff stuff) | |
| if (op == "~") | |
| { | |
| Array result; | |
| static if (hasLength!Stuff || isNarrowString!Stuff) | |
| result.reserve(length + stuff.length); | |
| else static if (hasSliceWithLength!Stuff) | |
| result.reserve(length + stuff[].length); | |
| else static if (isImplicitlyConvertible!(Stuff, T)) | |
| result.reserve(length + 1); | |
| result.insertBack(this[]); | |
| result ~= stuff; | |
| return result; | |
| } | |
| /** | |
| * Forwards to `insertBack`. | |
| */ | |
| void opOpAssign(string op, Stuff)(auto ref Stuff stuff) | |
| if (op == "~") | |
| { | |
| static if (is(typeof(stuff[])) && isImplicitlyConvertible!(typeof(stuff[0]), T)) | |
| { | |
| insertBack(stuff[]); | |
| } | |
| else | |
| { | |
| insertBack(stuff); | |
| } | |
| } | |
| /** | |
| * Removes all the elements from the array and releases allocated memory. | |
| * | |
| * Postcondition: `empty == true && capacity == 0` | |
| * | |
| * Complexity: $(BIGOH length) | |
| */ | |
| void clear() | |
| { | |
| _data = Data.init; | |
| } | |
| /** | |
| * Sets the number of elements in the array to `newLength`. If `newLength` | |
| * is greater than `length`, the new elements are added to the end of the | |
| * array and initialized with `T.init`. | |
| * | |
| * Complexity: | |
| * Guaranteed $(BIGOH abs(length - newLength)) if `capacity >= newLength`. | |
| * If `capacity < newLength` the worst case is $(BIGOH newLength). | |
| * | |
| * Postcondition: `length == newLength` | |
| */ | |
| @property void length(size_t newLength) | |
| { | |
| _data.refCountedStore.ensureInitialized(); | |
| _data.length = newLength; | |
| } | |
| /** | |
| * Removes the last element from the array and returns it. | |
| * Both stable and non-stable versions behave the same and guarantee | |
| * that ranges iterating over the array are never invalidated. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Returns: The element removed. | |
| * | |
| * Complexity: $(BIGOH 1). | |
| * | |
| * Throws: `Exception` if the array is empty. | |
| */ | |
| T removeAny() | |
| { | |
| auto result = back; | |
| removeBack(); | |
| return result; | |
| } | |
| /// ditto | |
| alias stableRemoveAny = removeAny; | |
| /** | |
| * Inserts the specified elements at the back of the array. `stuff` can be | |
| * a value convertible to `T` or a range of objects convertible to `T`. | |
| * | |
| * Returns: The number of elements inserted. | |
| * | |
| * Complexity: | |
| * $(BIGOH length + m) if reallocation takes place, otherwise $(BIGOH m), | |
| * where `m` is the number of elements in `stuff`. | |
| */ | |
| size_t insertBack(Stuff)(Stuff stuff) | |
| if (isImplicitlyConvertible!(Stuff, T) || | |
| isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) | |
| { | |
| _data.refCountedStore.ensureInitialized(); | |
| return _data.insertBack(stuff); | |
| } | |
| /// ditto | |
| alias insert = insertBack; | |
| /** | |
| * Removes the value from the back of the array. Both stable and non-stable | |
| * versions behave the same and guarantee that ranges iterating over the | |
| * array are never invalidated. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Complexity: $(BIGOH 1). | |
| * | |
| * Throws: `Exception` if the array is empty. | |
| */ | |
| void removeBack() | |
| { | |
| enforce(!empty); | |
| static if (hasElaborateDestructor!T) | |
| .destroy(_data._payload[$ - 1]); | |
| _data._payload = _data._payload[0 .. $ - 1]; | |
| } | |
| /// ditto | |
| alias stableRemoveBack = removeBack; | |
| /** | |
| * Removes `howMany` values from the back of the array. | |
| * Unlike the unparameterized versions above, these functions | |
| * do not throw if they could not remove `howMany` elements. Instead, | |
| * if `howMany > n`, all elements are removed. The returned value is | |
| * the effective number of elements removed. Both stable and non-stable | |
| * versions behave the same and guarantee that ranges iterating over | |
| * the array are never invalidated. | |
| * | |
| * Returns: The number of elements removed. | |
| * | |
| * Complexity: $(BIGOH howMany). | |
| */ | |
| size_t removeBack(size_t howMany) | |
| { | |
| if (howMany > length) howMany = length; | |
| static if (hasElaborateDestructor!T) | |
| foreach (ref e; _data._payload[$ - howMany .. $]) | |
| .destroy(e); | |
| _data._payload = _data._payload[0 .. $ - howMany]; | |
| return howMany; | |
| } | |
| /// ditto | |
| alias stableRemoveBack = removeBack; | |
| /** | |
| * Inserts `stuff` before, after, or instead range `r`, which must | |
| * be a valid range previously extracted from this array. `stuff` | |
| * can be a value convertible to `T` or a range of objects convertible | |
| * to `T`. Both stable and non-stable version behave the same and | |
| * guarantee that ranges iterating over the array are never invalidated. | |
| * | |
| * Returns: The number of values inserted. | |
| * | |
| * Complexity: $(BIGOH length + m), where `m` is the length of `stuff`. | |
| * | |
| * Throws: `Exception` if `r` is not a range extracted from this array. | |
| */ | |
| size_t insertBefore(Stuff)(Range r, Stuff stuff) | |
| if (isImplicitlyConvertible!(Stuff, T)) | |
| { | |
| import std.conv : emplace; | |
| enforce(r._outer._data is _data && r._a <= length); | |
| reserve(length + 1); | |
| assert(_data.refCountedStore.isInitialized); | |
| // Move elements over by one slot | |
| memmove(_data._payload.ptr + r._a + 1, | |
| _data._payload.ptr + r._a, | |
| T.sizeof * (length - r._a)); | |
| emplace(_data._payload.ptr + r._a, stuff); | |
| _data._payload = _data._payload.ptr[0 .. _data._payload.length + 1]; | |
| return 1; | |
| } | |
| /// ditto | |
| size_t insertBefore(Stuff)(Range r, Stuff stuff) | |
| if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) | |
| { | |
| import std.conv : emplace; | |
| enforce(r._outer._data is _data && r._a <= length); | |
| static if (isForwardRange!Stuff) | |
| { | |
| // Can find the length in advance | |
| auto extra = walkLength(stuff); | |
| if (!extra) return 0; | |
| reserve(length + extra); | |
| assert(_data.refCountedStore.isInitialized); | |
| // Move elements over by extra slots | |
| memmove(_data._payload.ptr + r._a + extra, | |
| _data._payload.ptr + r._a, | |
| T.sizeof * (length - r._a)); | |
| foreach (p; _data._payload.ptr + r._a .. | |
| _data._payload.ptr + r._a + extra) | |
| { | |
| emplace(p, stuff.front); | |
| stuff.popFront(); | |
| } | |
| _data._payload = | |
| _data._payload.ptr[0 .. _data._payload.length + extra]; | |
| return extra; | |
| } | |
| else | |
| { | |
| import std.algorithm.mutation : bringToFront; | |
| enforce(_data); | |
| immutable offset = r._a; | |
| enforce(offset <= length); | |
| auto result = insertBack(stuff); | |
| bringToFront(this[offset .. length - result], | |
| this[length - result .. length]); | |
| return result; | |
| } | |
| } | |
| /// ditto | |
| alias stableInsertBefore = insertBefore; | |
| /// ditto | |
| size_t insertAfter(Stuff)(Range r, Stuff stuff) | |
| { | |
| import std.algorithm.mutation : bringToFront; | |
| enforce(r._outer._data is _data); | |
| // TODO: optimize | |
| immutable offset = r._b; | |
| enforce(offset <= length); | |
| auto result = insertBack(stuff); | |
| bringToFront(this[offset .. length - result], | |
| this[length - result .. length]); | |
| return result; | |
| } | |
| /// ditto | |
| size_t replace(Stuff)(Range r, Stuff stuff) | |
| if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) | |
| { | |
| enforce(r._outer._data is _data); | |
| size_t result; | |
| for (; !stuff.empty; stuff.popFront()) | |
| { | |
| if (r.empty) | |
| { | |
| // insert the rest | |
| return result + insertBefore(r, stuff); | |
| } | |
| r.front = stuff.front; | |
| r.popFront(); | |
| ++result; | |
| } | |
| // Remove remaining stuff in r | |
| linearRemove(r); | |
| return result; | |
| } | |
| /// ditto | |
| size_t replace(Stuff)(Range r, Stuff stuff) | |
| if (isImplicitlyConvertible!(Stuff, T)) | |
| { | |
| enforce(r._outer._data is _data); | |
| if (r.empty) | |
| { | |
| insertBefore(r, stuff); | |
| } | |
| else | |
| { | |
| r.front = stuff; | |
| r.popFront(); | |
| linearRemove(r); | |
| } | |
| return 1; | |
| } | |
| /** | |
| * Removes all elements belonging to `r`, which must be a range | |
| * obtained originally from this array. | |
| * | |
| * Returns: A range spanning the remaining elements in the array that | |
| * initially were right after `r`. | |
| * | |
| * Complexity: $(BIGOH length) | |
| * | |
| * Throws: `Exception` if `r` is not a valid range extracted from this array. | |
| */ | |
| Range linearRemove(Range r) | |
| { | |
| import std.algorithm.mutation : copy; | |
| enforce(r._outer._data is _data); | |
| enforce(_data.refCountedStore.isInitialized); | |
| enforce(r._a <= r._b && r._b <= length); | |
| immutable offset1 = r._a; | |
| immutable offset2 = r._b; | |
| immutable tailLength = length - offset2; | |
| // Use copy here, not a[] = b[] because the ranges may overlap | |
| copy(this[offset2 .. length], this[offset1 .. offset1 + tailLength]); | |
| length = offset1 + tailLength; | |
| return this[length - tailLength .. length]; | |
| } | |
| } | |
| @system unittest | |
| { | |
| Array!int a; | |
| assert(a.empty); | |
| } | |
| @system unittest | |
| { | |
| Array!int a; | |
| a.length = 10; | |
| assert(a.length == 10); | |
| assert(a.capacity >= a.length); | |
| } | |
| @system unittest | |
| { | |
| struct Dumb { int x = 5; } | |
| Array!Dumb a; | |
| a.length = 10; | |
| assert(a.length == 10); | |
| assert(a.capacity >= a.length); | |
| immutable cap = a.capacity; | |
| foreach (ref e; a) | |
| e.x = 10; | |
| a.length = 5; | |
| assert(a.length == 5); | |
| // do not realloc if length decreases | |
| assert(a.capacity == cap); | |
| foreach (ref e; a) | |
| assert(e.x == 10); | |
| a.length = 8; | |
| assert(a.length == 8); | |
| // do not realloc if capacity sufficient | |
| assert(a.capacity == cap); | |
| assert(Dumb.init.x == 5); | |
| foreach (i; 0 .. 5) | |
| assert(a[i].x == 10); | |
| foreach (i; 5 .. a.length) | |
| assert(a[i].x == Dumb.init.x); | |
| // realloc required, check if values properly copied | |
| a[] = Dumb(1); | |
| a.length = 20; | |
| assert(a.capacity >= 20); | |
| foreach (i; 0 .. 8) | |
| assert(a[i].x == 1); | |
| foreach (i; 8 .. a.length) | |
| assert(a[i].x == Dumb.init.x); | |
| // check if overlapping elements properly initialized | |
| a.length = 1; | |
| a.length = 20; | |
| assert(a[0].x == 1); | |
| foreach (e; a[1 .. $]) | |
| assert(e.x == Dumb.init.x); | |
| } | |
| @system unittest | |
| { | |
| Array!int a = Array!int(1, 2, 3); | |
| //a._data._refCountedDebug = true; | |
| auto b = a.dup; | |
| assert(b == Array!int(1, 2, 3)); | |
| b.front = 42; | |
| assert(b == Array!int(42, 2, 3)); | |
| assert(a == Array!int(1, 2, 3)); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int(1, 2, 3); | |
| assert(a.length == 3); | |
| } | |
| @system unittest | |
| { | |
| const Array!int a = [1, 2]; | |
| assert(a[0] == 1); | |
| assert(a.front == 1); | |
| assert(a.back == 2); | |
| static assert(!__traits(compiles, { a[0] = 1; })); | |
| static assert(!__traits(compiles, { a.front = 1; })); | |
| static assert(!__traits(compiles, { a.back = 1; })); | |
| auto r = a[]; | |
| size_t i; | |
| foreach (e; r) | |
| { | |
| assert(e == i + 1); | |
| i++; | |
| } | |
| } | |
| @safe unittest | |
| { | |
| // REG https://issues.dlang.org/show_bug.cgi?id=13621 | |
| import std.container : Array, BinaryHeap; | |
| alias Heap = BinaryHeap!(Array!int); | |
| } | |
| @system unittest | |
| { | |
| Array!int a; | |
| a.reserve(1000); | |
| assert(a.length == 0); | |
| assert(a.empty); | |
| assert(a.capacity >= 1000); | |
| auto p = a._data._payload.ptr; | |
| foreach (i; 0 .. 1000) | |
| { | |
| a.insertBack(i); | |
| } | |
| assert(p == a._data._payload.ptr); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int(1, 2, 3); | |
| a[1] *= 42; | |
| assert(a[1] == 84); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int(1, 2, 3); | |
| auto b = Array!int(11, 12, 13); | |
| auto c = a ~ b; | |
| assert(c == Array!int(1, 2, 3, 11, 12, 13)); | |
| assert(a ~ b[] == Array!int(1, 2, 3, 11, 12, 13)); | |
| assert(a ~ [4,5] == Array!int(1,2,3,4,5)); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int(1, 2, 3); | |
| auto b = Array!int(11, 12, 13); | |
| a ~= b; | |
| assert(a == Array!int(1, 2, 3, 11, 12, 13)); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int(1, 2, 3, 4); | |
| assert(a.removeAny() == 4); | |
| assert(a == Array!int(1, 2, 3)); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int(1, 2, 3, 4, 5); | |
| auto r = a[2 .. a.length]; | |
| assert(a.insertBefore(r, 42) == 1); | |
| assert(a == Array!int(1, 2, 42, 3, 4, 5)); | |
| r = a[2 .. 2]; | |
| assert(a.insertBefore(r, [8, 9]) == 2); | |
| assert(a == Array!int(1, 2, 8, 9, 42, 3, 4, 5)); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int(0, 1, 2, 3, 4, 5, 6, 7, 8); | |
| a.linearRemove(a[4 .. 6]); | |
| assert(a == Array!int(0, 1, 2, 3, 6, 7, 8)); | |
| } | |
| // Give the Range object some testing. | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.range : retro; | |
| auto a = Array!int(0, 1, 2, 3, 4, 5, 6)[]; | |
| auto b = Array!int(6, 5, 4, 3, 2, 1, 0)[]; | |
| alias A = typeof(a); | |
| static assert(isRandomAccessRange!A); | |
| static assert(hasSlicing!A); | |
| static assert(hasAssignableElements!A); | |
| static assert(hasMobileElements!A); | |
| assert(equal(retro(b), a)); | |
| assert(a.length == 7); | |
| assert(equal(a[1 .. 4], [1, 2, 3])); | |
| } | |
| // Test issue 5920 | |
| @system unittest | |
| { | |
| struct structBug5920 | |
| { | |
| int order; | |
| uint* pDestructionMask; | |
| ~this() | |
| { | |
| if (pDestructionMask) | |
| *pDestructionMask += 1 << order; | |
| } | |
| } | |
| alias S = structBug5920; | |
| uint dMask; | |
| auto arr = Array!S(cast(S[])[]); | |
| foreach (i; 0 .. 8) | |
| arr.insertBack(S(i, &dMask)); | |
| // don't check dMask now as S may be copied multiple times (it's ok?) | |
| { | |
| assert(arr.length == 8); | |
| dMask = 0; | |
| arr.length = 6; | |
| assert(arr.length == 6); // make sure shrinking calls the d'tor | |
| assert(dMask == 0b1100_0000); | |
| arr.removeBack(); | |
| assert(arr.length == 5); // make sure removeBack() calls the d'tor | |
| assert(dMask == 0b1110_0000); | |
| arr.removeBack(3); | |
| assert(arr.length == 2); // ditto | |
| assert(dMask == 0b1111_1100); | |
| arr.clear(); | |
| assert(arr.length == 0); // make sure clear() calls the d'tor | |
| assert(dMask == 0b1111_1111); | |
| } | |
| assert(dMask == 0b1111_1111); // make sure the d'tor is called once only. | |
| } | |
| // Test issue 5792 (mainly just to check if this piece of code is compilable) | |
| @system unittest | |
| { | |
| auto a = Array!(int[])([[1,2],[3,4]]); | |
| a.reserve(4); | |
| assert(a.capacity >= 4); | |
| assert(a.length == 2); | |
| assert(a[0] == [1,2]); | |
| assert(a[1] == [3,4]); | |
| a.reserve(16); | |
| assert(a.capacity >= 16); | |
| assert(a.length == 2); | |
| assert(a[0] == [1,2]); | |
| assert(a[1] == [3,4]); | |
| } | |
| // test replace!Stuff with range Stuff | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto a = Array!int([1, 42, 5]); | |
| a.replace(a[1 .. 2], [2, 3, 4]); | |
| assert(equal(a[], [1, 2, 3, 4, 5])); | |
| } | |
| // test insertBefore and replace with empty Arrays | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto a = Array!int(); | |
| a.insertBefore(a[], 1); | |
| assert(equal(a[], [1])); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto a = Array!int(); | |
| a.insertBefore(a[], [1, 2]); | |
| assert(equal(a[], [1, 2])); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto a = Array!int(); | |
| a.replace(a[], [1, 2]); | |
| assert(equal(a[], [1, 2])); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto a = Array!int(); | |
| a.replace(a[], 1); | |
| assert(equal(a[], [1])); | |
| } | |
| // make sure that Array instances refuse ranges that don't belong to them | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| Array!int a = [1, 2, 3]; | |
| auto r = a.dup[]; | |
| assertThrown(a.insertBefore(r, 42)); | |
| assertThrown(a.insertBefore(r, [42])); | |
| assertThrown(a.insertAfter(r, 42)); | |
| assertThrown(a.replace(r, 42)); | |
| assertThrown(a.replace(r, [42])); | |
| assertThrown(a.linearRemove(r)); | |
| } | |
| @system unittest | |
| { | |
| auto a = Array!int([1, 1]); | |
| a[1] = 0; //Check Array.opIndexAssign | |
| assert(a[1] == 0); | |
| a[1] += 1; //Check Array.opIndexOpAssign | |
| assert(a[1] == 1); | |
| //Check Array.opIndexUnary | |
| ++a[0]; | |
| //a[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed | |
| assert(a[0] == 2); | |
| assert(+a[0] == +2); | |
| assert(-a[0] == -2); | |
| assert(~a[0] == ~2); | |
| auto r = a[]; | |
| r[1] = 0; //Check Array.Range.opIndexAssign | |
| assert(r[1] == 0); | |
| r[1] += 1; //Check Array.Range.opIndexOpAssign | |
| assert(r[1] == 1); | |
| //Check Array.Range.opIndexUnary | |
| ++r[0]; | |
| //r[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed | |
| assert(r[0] == 3); | |
| assert(+r[0] == +3); | |
| assert(-r[0] == -3); | |
| assert(~r[0] == ~3); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| //Test "array-wide" operations | |
| auto a = Array!int([0, 1, 2]); //Array | |
| a[] += 5; | |
| assert(a[].equal([5, 6, 7])); | |
| ++a[]; | |
| assert(a[].equal([6, 7, 8])); | |
| a[1 .. 3] *= 5; | |
| assert(a[].equal([6, 35, 40])); | |
| a[0 .. 2] = 0; | |
| assert(a[].equal([0, 0, 40])); | |
| //Test empty array | |
| auto a2 = Array!int.init; | |
| ++a2[]; | |
| ++a2[0 .. 0]; | |
| a2[] = 0; | |
| a2[0 .. 0] = 0; | |
| a2[] += 0; | |
| a2[0 .. 0] += 0; | |
| //Test "range-wide" operations | |
| auto r = Array!int([0, 1, 2])[]; //Array.Range | |
| r[] += 5; | |
| assert(r.equal([5, 6, 7])); | |
| ++r[]; | |
| assert(r.equal([6, 7, 8])); | |
| r[1 .. 3] *= 5; | |
| assert(r.equal([6, 35, 40])); | |
| r[0 .. 2] = 0; | |
| assert(r.equal([0, 0, 40])); | |
| //Test empty Range | |
| auto r2 = Array!int.init[]; | |
| ++r2[]; | |
| ++r2[0 .. 0]; | |
| r2[] = 0; | |
| r2[0 .. 0] = 0; | |
| r2[] += 0; | |
| r2[0 .. 0] += 0; | |
| } | |
| // Test issue 11194 | |
| @system unittest | |
| { | |
| static struct S { | |
| int i = 1337; | |
| void* p; | |
| this(this) { assert(i == 1337); } | |
| ~this() { assert(i == 1337); } | |
| } | |
| Array!S arr; | |
| S s; | |
| arr ~= s; | |
| arr ~= s; | |
| } | |
| @safe unittest //11459 | |
| { | |
| static struct S | |
| { | |
| bool b; | |
| alias b this; | |
| } | |
| alias A = Array!S; | |
| alias B = Array!(shared bool); | |
| } | |
| @system unittest //11884 | |
| { | |
| import std.algorithm.iteration : filter; | |
| auto a = Array!int([1, 2, 2].filter!"true"()); | |
| } | |
| @safe unittest //8282 | |
| { | |
| auto arr = new Array!int; | |
| } | |
| @system unittest //6998 | |
| { | |
| static int i = 0; | |
| class C | |
| { | |
| int dummy = 1; | |
| this(){++i;} | |
| ~this(){--i;} | |
| } | |
| assert(i == 0); | |
| auto c = new C(); | |
| assert(i == 1); | |
| //scope | |
| { | |
| auto arr = Array!C(c); | |
| assert(i == 1); | |
| } | |
| //Array should not have destroyed the class instance | |
| assert(i == 1); | |
| //Just to make sure the GC doesn't collect before the above test. | |
| assert(c.dummy == 1); | |
| } | |
| @system unittest //6998-2 | |
| { | |
| static class C {int i;} | |
| auto c = new C; | |
| c.i = 42; | |
| Array!C a; | |
| a ~= c; | |
| a.clear; | |
| assert(c.i == 42); //fails | |
| } | |
| @safe unittest | |
| { | |
| static assert(is(Array!int.Range)); | |
| static assert(is(Array!int.ConstRange)); | |
| } | |
| @system unittest // const/immutable Array and Ranges | |
| { | |
| static void test(A, R, E, S)() | |
| { | |
| A a; | |
| R r = a[]; | |
| assert(r.empty); | |
| assert(r.length == 0); | |
| static assert(is(typeof(r.front) == E)); | |
| static assert(is(typeof(r.back) == E)); | |
| static assert(is(typeof(r[0]) == E)); | |
| static assert(is(typeof(r[]) == S)); | |
| static assert(is(typeof(r[0 .. 0]) == S)); | |
| } | |
| alias A = Array!int; | |
| test!(A, A.Range, int, A.Range); | |
| test!(A, const A.Range, const int, A.ConstRange); | |
| test!(const A, A.ConstRange, const int, A.ConstRange); | |
| test!(const A, const A.ConstRange, const int, A.ConstRange); | |
| test!(immutable A, A.ImmutableRange, immutable int, A.ImmutableRange); | |
| test!(immutable A, const A.ImmutableRange, immutable int, A.ImmutableRange); | |
| test!(immutable A, immutable A.ImmutableRange, immutable int, | |
| A.ImmutableRange); | |
| } | |
| // ensure @nogc | |
| @nogc @system unittest | |
| { | |
| Array!int ai; | |
| ai ~= 1; | |
| assert(ai.front == 1); | |
| ai.reserve(10); | |
| assert(ai.capacity == 10); | |
| static immutable arr = [1, 2, 3]; | |
| ai.insertBack(arr); | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Array!bool | |
| //////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * _Array specialized for `bool`. Packs together values efficiently by | |
| * allocating one bit per element. | |
| */ | |
| struct Array(T) | |
| if (is(Unqual!T == bool)) | |
| { | |
| import std.exception : enforce; | |
| import std.typecons : RefCounted, RefCountedAutoInitialize; | |
| static immutable uint bitsPerWord = size_t.sizeof * 8; | |
| private static struct Data | |
| { | |
| Array!size_t.Payload _backend; | |
| size_t _length; | |
| } | |
| private RefCounted!(Data, RefCountedAutoInitialize.no) _store; | |
| private @property ref size_t[] data() | |
| { | |
| assert(_store.refCountedStore.isInitialized); | |
| return _store._backend._payload; | |
| } | |
| /** | |
| * Defines the array's primary range. | |
| */ | |
| struct Range | |
| { | |
| private Array _outer; | |
| private size_t _a, _b; | |
| /// Range primitives | |
| @property Range save() | |
| { | |
| version (bug4437) | |
| { | |
| return this; | |
| } | |
| else | |
| { | |
| auto copy = this; | |
| return copy; | |
| } | |
| } | |
| /// Ditto | |
| @property bool empty() | |
| { | |
| return _a >= _b || _outer.length < _b; | |
| } | |
| /// Ditto | |
| @property T front() | |
| { | |
| enforce(!empty, "Attempting to access the front of an empty Array"); | |
| return _outer[_a]; | |
| } | |
| /// Ditto | |
| @property void front(bool value) | |
| { | |
| enforce(!empty, "Attempting to set the front of an empty Array"); | |
| _outer[_a] = value; | |
| } | |
| /// Ditto | |
| T moveFront() | |
| { | |
| enforce(!empty, "Attempting to move the front of an empty Array"); | |
| return _outer.moveAt(_a); | |
| } | |
| /// Ditto | |
| void popFront() | |
| { | |
| enforce(!empty, "Attempting to popFront an empty Array"); | |
| ++_a; | |
| } | |
| /// Ditto | |
| @property T back() | |
| { | |
| enforce(!empty, "Attempting to access the back of an empty Array"); | |
| return _outer[_b - 1]; | |
| } | |
| /// Ditto | |
| @property void back(bool value) | |
| { | |
| enforce(!empty, "Attempting to set the back of an empty Array"); | |
| _outer[_b - 1] = value; | |
| } | |
| /// Ditto | |
| T moveBack() | |
| { | |
| enforce(!empty, "Attempting to move the back of an empty Array"); | |
| return _outer.moveAt(_b - 1); | |
| } | |
| /// Ditto | |
| void popBack() | |
| { | |
| enforce(!empty, "Attempting to popBack an empty Array"); | |
| --_b; | |
| } | |
| /// Ditto | |
| T opIndex(size_t i) | |
| { | |
| return _outer[_a + i]; | |
| } | |
| /// Ditto | |
| void opIndexAssign(T value, size_t i) | |
| { | |
| _outer[_a + i] = value; | |
| } | |
| /// Ditto | |
| T moveAt(size_t i) | |
| { | |
| return _outer.moveAt(_a + i); | |
| } | |
| /// Ditto | |
| @property size_t length() const | |
| { | |
| assert(_a <= _b); | |
| return _b - _a; | |
| } | |
| alias opDollar = length; | |
| /// ditto | |
| Range opSlice(size_t low, size_t high) | |
| { | |
| assert( | |
| _a <= low && low <= high && high <= _b, | |
| "Using out of bounds indexes on an Array" | |
| ); | |
| return Range(_outer, _a + low, _a + high); | |
| } | |
| } | |
| /** | |
| * Property returning `true` if and only if the array has | |
| * no elements. | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| @property bool empty() | |
| { | |
| return !length; | |
| } | |
| /** | |
| * Returns: A duplicate of the array. | |
| * | |
| * Complexity: $(BIGOH length). | |
| */ | |
| @property Array dup() | |
| { | |
| Array result; | |
| result.insertBack(this[]); | |
| return result; | |
| } | |
| /** | |
| * Returns the number of elements in the array. | |
| * | |
| * Complexity: $(BIGOH 1). | |
| */ | |
| @property size_t length() const | |
| { | |
| return _store.refCountedStore.isInitialized ? _store._length : 0; | |
| } | |
| size_t opDollar() const | |
| { | |
| return length; | |
| } | |
| /** | |
| * Returns: The maximum number of elements the array can store without | |
| * reallocating memory and invalidating iterators upon insertion. | |
| * | |
| * Complexity: $(BIGOH 1). | |
| */ | |
| @property size_t capacity() | |
| { | |
| return _store.refCountedStore.isInitialized | |
| ? cast(size_t) bitsPerWord * _store._backend.capacity | |
| : 0; | |
| } | |
| /** | |
| * Ensures sufficient capacity to accommodate `e` _elements. | |
| * If `e < capacity`, this method does nothing. | |
| * | |
| * Postcondition: `capacity >= e` | |
| * | |
| * Note: If the capacity is increased, one should assume that all | |
| * iterators to the elements are invalidated. | |
| * | |
| * Complexity: at most $(BIGOH length) if `e > capacity`, otherwise $(BIGOH 1). | |
| */ | |
| void reserve(size_t e) | |
| { | |
| import std.conv : to; | |
| _store.refCountedStore.ensureInitialized(); | |
| _store._backend.reserve(to!size_t((e + bitsPerWord - 1) / bitsPerWord)); | |
| } | |
| /** | |
| * Returns: A range that iterates over all elements of the array in forward order. | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| Range opSlice() | |
| { | |
| return Range(this, 0, length); | |
| } | |
| /** | |
| * Returns: A range that iterates the array between two specified positions. | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| Range opSlice(size_t a, size_t b) | |
| { | |
| enforce(a <= b && b <= length); | |
| return Range(this, a, b); | |
| } | |
| /** | |
| * Returns: The first element of the array. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Complexity: $(BIGOH 1) | |
| * | |
| * Throws: `Exception` if the array is empty. | |
| */ | |
| @property bool front() | |
| { | |
| enforce(!empty); | |
| return data.ptr[0] & 1; | |
| } | |
| /// Ditto | |
| @property void front(bool value) | |
| { | |
| enforce(!empty); | |
| if (value) data.ptr[0] |= 1; | |
| else data.ptr[0] &= ~cast(size_t) 1; | |
| } | |
| /** | |
| * Returns: The last element of the array. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Complexity: $(BIGOH 1) | |
| * | |
| * Throws: `Exception` if the array is empty. | |
| */ | |
| @property bool back() | |
| { | |
| enforce(!empty); | |
| return cast(bool)(data.back & (cast(size_t) 1 << ((_store._length - 1) % bitsPerWord))); | |
| } | |
| /// Ditto | |
| @property void back(bool value) | |
| { | |
| enforce(!empty); | |
| if (value) | |
| { | |
| data.back |= (cast(size_t) 1 << ((_store._length - 1) % bitsPerWord)); | |
| } | |
| else | |
| { | |
| data.back &= | |
| ~(cast(size_t) 1 << ((_store._length - 1) % bitsPerWord)); | |
| } | |
| } | |
| /** | |
| * Indexing operators yielding or modifyng the value at the specified index. | |
| * | |
| * Precondition: `i < length` | |
| * | |
| * Complexity: $(BIGOH 1) | |
| */ | |
| bool opIndex(size_t i) | |
| { | |
| auto div = cast(size_t) (i / bitsPerWord); | |
| auto rem = i % bitsPerWord; | |
| enforce(div < data.length); | |
| return cast(bool)(data.ptr[div] & (cast(size_t) 1 << rem)); | |
| } | |
| /// ditto | |
| void opIndexAssign(bool value, size_t i) | |
| { | |
| auto div = cast(size_t) (i / bitsPerWord); | |
| auto rem = i % bitsPerWord; | |
| enforce(div < data.length); | |
| if (value) data.ptr[div] |= (cast(size_t) 1 << rem); | |
| else data.ptr[div] &= ~(cast(size_t) 1 << rem); | |
| } | |
| /// ditto | |
| void opIndexOpAssign(string op)(bool value, size_t i) | |
| { | |
| auto div = cast(size_t) (i / bitsPerWord); | |
| auto rem = i % bitsPerWord; | |
| enforce(div < data.length); | |
| auto oldValue = cast(bool) (data.ptr[div] & (cast(size_t) 1 << rem)); | |
| // Do the deed | |
| auto newValue = mixin("oldValue "~op~" value"); | |
| // Write back the value | |
| if (newValue != oldValue) | |
| { | |
| if (newValue) data.ptr[div] |= (cast(size_t) 1 << rem); | |
| else data.ptr[div] &= ~(cast(size_t) 1 << rem); | |
| } | |
| } | |
| /// Ditto | |
| T moveAt(size_t i) | |
| { | |
| return this[i]; | |
| } | |
| /** | |
| * Returns: A new array which is a concatenation of `this` and its argument. | |
| * | |
| * Complexity: | |
| * $(BIGOH length + m), where `m` is the number of elements in `stuff`. | |
| */ | |
| Array!bool opBinary(string op, Stuff)(Stuff rhs) | |
| if (op == "~") | |
| { | |
| Array!bool result; | |
| static if (hasLength!Stuff) | |
| result.reserve(length + rhs.length); | |
| else static if (is(typeof(rhs[])) && hasLength!(typeof(rhs[]))) | |
| result.reserve(length + rhs[].length); | |
| else static if (isImplicitlyConvertible!(Stuff, bool)) | |
| result.reserve(length + 1); | |
| result.insertBack(this[]); | |
| result ~= rhs; | |
| return result; | |
| } | |
| /** | |
| * Forwards to `insertBack`. | |
| */ | |
| Array!bool opOpAssign(string op, Stuff)(Stuff stuff) | |
| if (op == "~") | |
| { | |
| static if (is(typeof(stuff[]))) insertBack(stuff[]); | |
| else insertBack(stuff); | |
| return this; | |
| } | |
| /** | |
| * Removes all the elements from the array and releases allocated memory. | |
| * | |
| * Postcondition: `empty == true && capacity == 0` | |
| * | |
| * Complexity: $(BIGOH length) | |
| */ | |
| void clear() | |
| { | |
| this = Array(); | |
| } | |
| /** | |
| * Sets the number of elements in the array to `newLength`. If `newLength` | |
| * is greater than `length`, the new elements are added to the end of the | |
| * array and initialized with `false`. | |
| * | |
| * Complexity: | |
| * Guaranteed $(BIGOH abs(length - newLength)) if `capacity >= newLength`. | |
| * If `capacity < newLength` the worst case is $(BIGOH newLength). | |
| * | |
| * Postcondition: `length == newLength` | |
| */ | |
| @property void length(size_t newLength) | |
| { | |
| import std.conv : to; | |
| _store.refCountedStore.ensureInitialized(); | |
| auto newDataLength = | |
| to!size_t((newLength + bitsPerWord - 1) / bitsPerWord); | |
| _store._backend.length = newDataLength; | |
| _store._length = newLength; | |
| } | |
| /** | |
| * Removes the last element from the array and returns it. | |
| * Both stable and non-stable versions behave the same and guarantee | |
| * that ranges iterating over the array are never invalidated. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Returns: The element removed. | |
| * | |
| * Complexity: $(BIGOH 1). | |
| * | |
| * Throws: `Exception` if the array is empty. | |
| */ | |
| T removeAny() | |
| { | |
| auto result = back; | |
| removeBack(); | |
| return result; | |
| } | |
| /// ditto | |
| alias stableRemoveAny = removeAny; | |
| /** | |
| * Inserts the specified elements at the back of the array. `stuff` can be | |
| * a value convertible to `bool` or a range of objects convertible to `bool`. | |
| * | |
| * Returns: The number of elements inserted. | |
| * | |
| * Complexity: | |
| * $(BIGOH length + m) if reallocation takes place, otherwise $(BIGOH m), | |
| * where `m` is the number of elements in `stuff`. | |
| */ | |
| size_t insertBack(Stuff)(Stuff stuff) | |
| if (is(Stuff : bool)) | |
| { | |
| _store.refCountedStore.ensureInitialized(); | |
| auto rem = _store._length % bitsPerWord; | |
| if (rem) | |
| { | |
| // Fits within the current array | |
| if (stuff) | |
| { | |
| data[$ - 1] |= (cast(size_t) 1 << rem); | |
| } | |
| else | |
| { | |
| data[$ - 1] &= ~(cast(size_t) 1 << rem); | |
| } | |
| } | |
| else | |
| { | |
| // Need to add more data | |
| _store._backend.insertBack(stuff); | |
| } | |
| ++_store._length; | |
| return 1; | |
| } | |
| /// ditto | |
| size_t insertBack(Stuff)(Stuff stuff) | |
| if (isInputRange!Stuff && is(ElementType!Stuff : bool)) | |
| { | |
| static if (!hasLength!Stuff) size_t result; | |
| for (; !stuff.empty; stuff.popFront()) | |
| { | |
| insertBack(stuff.front); | |
| static if (!hasLength!Stuff) ++result; | |
| } | |
| static if (!hasLength!Stuff) return result; | |
| else return stuff.length; | |
| } | |
| /// ditto | |
| alias stableInsertBack = insertBack; | |
| /// ditto | |
| alias insert = insertBack; | |
| /// ditto | |
| alias stableInsert = insertBack; | |
| /// ditto | |
| alias linearInsert = insertBack; | |
| /// ditto | |
| alias stableLinearInsert = insertBack; | |
| /** | |
| * Removes the value from the back of the array. Both stable and non-stable | |
| * versions behave the same and guarantee that ranges iterating over the | |
| * array are never invalidated. | |
| * | |
| * Precondition: `empty == false` | |
| * | |
| * Complexity: $(BIGOH 1). | |
| * | |
| * Throws: `Exception` if the array is empty. | |
| */ | |
| void removeBack() | |
| { | |
| enforce(_store._length); | |
| if (_store._length % bitsPerWord) | |
| { | |
| // Cool, just decrease the length | |
| --_store._length; | |
| } | |
| else | |
| { | |
| // Reduce the allocated space | |
| --_store._length; | |
| _store._backend.length = _store._backend.length - 1; | |
| } | |
| } | |
| /// ditto | |
| alias stableRemoveBack = removeBack; | |
| /** | |
| * Removes `howMany` values from the back of the array. Unlike the | |
| * unparameterized versions above, these functions do not throw if | |
| * they could not remove `howMany` elements. Instead, if `howMany > n`, | |
| * all elements are removed. The returned value is the effective number | |
| * of elements removed. Both stable and non-stable versions behave the same | |
| * and guarantee that ranges iterating over the array are never invalidated. | |
| * | |
| * Returns: The number of elements removed. | |
| * | |
| * Complexity: $(BIGOH howMany). | |
| */ | |
| size_t removeBack(size_t howMany) | |
| { | |
| if (howMany >= length) | |
| { | |
| howMany = length; | |
| clear(); | |
| } | |
| else | |
| { | |
| length = length - howMany; | |
| } | |
| return howMany; | |
| } | |
| /// ditto | |
| alias stableRemoveBack = removeBack; | |
| /** | |
| * Inserts `stuff` before, after, or instead range `r`, which must | |
| * be a valid range previously extracted from this array. `stuff` | |
| * can be a value convertible to `bool` or a range of objects convertible | |
| * to `bool`. Both stable and non-stable version behave the same and | |
| * guarantee that ranges iterating over the array are never invalidated. | |
| * | |
| * Returns: The number of values inserted. | |
| * | |
| * Complexity: $(BIGOH length + m), where `m` is the length of `stuff`. | |
| */ | |
| size_t insertBefore(Stuff)(Range r, Stuff stuff) | |
| { | |
| import std.algorithm.mutation : bringToFront; | |
| // TODO: make this faster, it moves one bit at a time | |
| immutable inserted = stableInsertBack(stuff); | |
| immutable tailLength = length - inserted; | |
| bringToFront( | |
| this[r._a .. tailLength], | |
| this[tailLength .. length]); | |
| return inserted; | |
| } | |
| /// ditto | |
| alias stableInsertBefore = insertBefore; | |
| /// ditto | |
| size_t insertAfter(Stuff)(Range r, Stuff stuff) | |
| { | |
| import std.algorithm.mutation : bringToFront; | |
| // TODO: make this faster, it moves one bit at a time | |
| immutable inserted = stableInsertBack(stuff); | |
| immutable tailLength = length - inserted; | |
| bringToFront( | |
| this[r._b .. tailLength], | |
| this[tailLength .. length]); | |
| return inserted; | |
| } | |
| /// ditto | |
| alias stableInsertAfter = insertAfter; | |
| /// ditto | |
| size_t replace(Stuff)(Range r, Stuff stuff) | |
| if (is(Stuff : bool)) | |
| { | |
| if (!r.empty) | |
| { | |
| // There is room | |
| r.front = stuff; | |
| r.popFront(); | |
| linearRemove(r); | |
| } | |
| else | |
| { | |
| // No room, must insert | |
| insertBefore(r, stuff); | |
| } | |
| return 1; | |
| } | |
| /// ditto | |
| alias stableReplace = replace; | |
| /** | |
| * Removes all elements belonging to `r`, which must be a range | |
| * obtained originally from this array. | |
| * | |
| * Returns: A range spanning the remaining elements in the array that | |
| * initially were right after `r`. | |
| * | |
| * Complexity: $(BIGOH length) | |
| */ | |
| Range linearRemove(Range r) | |
| { | |
| import std.algorithm.mutation : copy; | |
| copy(this[r._b .. length], this[r._a .. length]); | |
| length = length - r.length; | |
| return this[r._a .. length]; | |
| } | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| assert(a.empty); | |
| } | |
| @system unittest | |
| { | |
| Array!bool arr; | |
| arr.insert([false, false, false, false]); | |
| assert(arr.front == false); | |
| assert(arr.back == false); | |
| assert(arr[1] == false); | |
| auto slice = arr[]; | |
| slice = arr[0 .. $]; | |
| slice = slice[1 .. $]; | |
| slice.front = true; | |
| slice.back = true; | |
| slice[1] = true; | |
| assert(slice.front == true); | |
| assert(slice.back == true); | |
| assert(slice[1] == true); | |
| assert(slice.moveFront == true); | |
| assert(slice.moveBack == true); | |
| assert(slice.moveAt(1) == true); | |
| } | |
| // issue 16331 - uncomparable values are valid values for an array | |
| @system unittest | |
| { | |
| double[] values = [double.nan, double.nan]; | |
| auto arr = Array!double(values); | |
| } | |
| @nogc @system unittest | |
| { | |
| auto a = Array!int(0, 1, 2); | |
| int[3] b = [3, 4, 5]; | |
| short[3] ci = [0, 1, 0]; | |
| auto c = Array!short(ci); | |
| assert(Array!int(0, 1, 2, 0, 1, 2) == a ~ a); | |
| assert(Array!int(0, 1, 2, 3, 4, 5) == a ~ b); | |
| assert(Array!int(0, 1, 2, 3) == a ~ 3); | |
| assert(Array!int(0, 1, 2, 0, 1, 0) == a ~ c); | |
| } | |
| @nogc @system unittest | |
| { | |
| auto a = Array!char('a', 'b'); | |
| assert(Array!char("abc") == a ~ 'c'); | |
| import std.utf : byCodeUnit; | |
| assert(Array!char("abcd") == a ~ "cd".byCodeUnit); | |
| } | |
| @nogc @system unittest | |
| { | |
| auto a = Array!dchar("ąćę"d); | |
| assert(Array!dchar("ąćęϢϖ"d) == a ~ "Ϣϖ"d); | |
| wchar x = 'Ï¢'; | |
| assert(Array!dchar("ąćęϢz"d) == a ~ x ~ 'z'); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| assert(a.empty); | |
| a.insertBack(false); | |
| assert(!a.empty); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| assert(a.empty); | |
| auto b = a.dup; | |
| assert(b.empty); | |
| a.insertBack(true); | |
| assert(b.empty); | |
| } | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| Array!bool a; | |
| assert(a.length == 0); | |
| a.insert(true); | |
| assert(a.length == 1, to!string(a.length)); | |
| } | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| Array!bool a; | |
| assert(a.capacity == 0); | |
| foreach (i; 0 .. 100) | |
| { | |
| a.insert(true); | |
| assert(a.capacity >= a.length, to!string(a.capacity)); | |
| } | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| assert(a.capacity == 0); | |
| a.reserve(15657); | |
| assert(a.capacity >= 15657); | |
| a.reserve(100); | |
| assert(a.capacity >= 15657); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| assert(a[0 .. 2].length == 2); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| assert(a[].length == 4); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| assert(a.front); | |
| a.front = false; | |
| assert(!a.front); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| assert(a[].length == 4); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| assert(a.back); | |
| a.back = false; | |
| assert(!a.back); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| assert(a[0] && !a[1]); | |
| a[0] &= a[1]; | |
| assert(!a[0]); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| Array!bool b; | |
| b.insertBack([true, true, false, true]); | |
| assert(equal((a ~ b)[], | |
| [true, false, true, true, true, true, false, true])); | |
| assert((a ~ [true, false])[].equal([true, false, true, true, true, false])); | |
| Array!bool c; | |
| c.insertBack(true); | |
| assert((c ~ false)[].equal([true, false])); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| Array!bool b; | |
| a.insertBack([false, true, false, true, true]); | |
| a ~= b; | |
| assert(equal( | |
| a[], | |
| [true, false, true, true, false, true, false, true, true])); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.insertBack([true, false, true, true]); | |
| a.clear(); | |
| assert(a.capacity == 0); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.length = 1057; | |
| assert(a.length == 1057); | |
| assert(a.capacity >= a.length); | |
| foreach (e; a) | |
| { | |
| assert(!e); | |
| } | |
| immutable cap = a.capacity; | |
| a.length = 100; | |
| assert(a.length == 100); | |
| // do not realloc if length decreases | |
| assert(a.capacity == cap); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.length = 1057; | |
| assert(!a.removeAny()); | |
| assert(a.length == 1056); | |
| foreach (e; a) | |
| { | |
| assert(!e); | |
| } | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| for (int i = 0; i < 100; ++i) | |
| a.insertBack(true); | |
| foreach (e; a) | |
| assert(e); | |
| } | |
| @system unittest | |
| { | |
| Array!bool a; | |
| a.length = 1057; | |
| assert(a.removeBack(1000) == 1000); | |
| assert(a.length == 57); | |
| foreach (e; a) | |
| { | |
| assert(!e); | |
| } | |
| } | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| Array!bool a; | |
| version (bugxxxx) | |
| { | |
| a._store.refCountedDebug = true; | |
| } | |
| a.insertBefore(a[], true); | |
| assert(a.length == 1, to!string(a.length)); | |
| a.insertBefore(a[], false); | |
| assert(a.length == 2, to!string(a.length)); | |
| a.insertBefore(a[1 .. $], true); | |
| import std.algorithm.comparison : equal; | |
| assert(a[].equal([false, true, true])); | |
| } | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| Array!bool a; | |
| a.length = 10; | |
| a.insertAfter(a[0 .. 5], true); | |
| assert(a.length == 11, to!string(a.length)); | |
| assert(a[5]); | |
| } | |
| @system unittest | |
| { | |
| alias V3 = int[3]; | |
| V3 v = [1, 2, 3]; | |
| Array!V3 arr; | |
| arr ~= v; | |
| assert(arr[0] == [1, 2, 3]); | |
| } | |
| @system unittest | |
| { | |
| alias V3 = int[3]; | |
| V3[2] v = [[1, 2, 3], [4, 5, 6]]; | |
| Array!V3 arr; | |
| arr ~= v; | |
| assert(arr[0] == [1, 2, 3]); | |
| assert(arr[1] == [4, 5, 6]); | |
| } |