diff --git a/source/tanya/container/vector.d b/source/tanya/container/vector.d index 44295f8..2648363 100644 --- a/source/tanya/container/vector.d +++ b/source/tanya/container/vector.d @@ -10,22 +10,12 @@ */ module tanya.container.vector; +import std.algorithm.comparison; +import std.traits; import tanya.memory; /** - * One dimensional array. It allocates automatically if needed. - * - * If you assign a value: - * --- - * auto v = make!(Vector!int)(theAllocator); - * int value = 5; - * - * v[1000] = value; - * - * dispose(theAllocator, v); - * --- - * it will allocate not only for one, but for 1000 elements. So this - * implementation is more suitable for sequential data with random access. + * One dimensional array. * * Params: * T = Content type. @@ -33,33 +23,241 @@ import tanya.memory; class Vector(T) { /** - * Creates a new $(D_PSYMBOL Vector). - * - * Params: - * length = Initial length. - * allocator = The allocator should be used for the element - * allocations. + * Defines the container's primary range. */ - this(size_t length, IAllocator allocator = theAllocator) + struct Range(V) { - this.allocator = allocator; - vector = makeArray!T(allocator, length); - } + private V[1] data; - /// Ditto. - this(IAllocator allocator = theAllocator) - { - this(0, allocator); + private @property ref inout(V) outer() inout + { + return data[0]; + } + + private size_t start, end; + + invariant + { + assert(start <= end); + } + + private alias ElementType = typeof(data[0].vector[0]); + + private this(V data, in size_t a, in size_t b) + { + this.data = data; + start = a; + end = b; + } + + @property Range save() + { + return this; + } + + @property bool empty() const + { + return start >= end; + } + + @property size_t length() const + { + return end - start; + } + + alias opDollar = length; + + @property ref inout(ElementType) front() inout + in + { + assert(!empty); + } + body + { + return outer[start]; + } + + @property ref inout(ElementType) back() inout + in + { + assert(!empty); + } + body + { + return outer[end - 1]; + } + + void popFront() + in + { + assert(!empty); + } + body + { + ++start; + } + + void popBack() + in + { + assert(!empty); + } + body + { + --end; + } + + ref inout(ElementType) opIndex(in size_t i) inout + in + { + assert(start + i < end); + } + body + { + return outer[start + i]; + } + + Range opIndex() + { + return typeof(return)(outer, start, end); + } + + Range opSlice(in size_t i, in size_t j) + in + { + assert(i <= j); + assert(start + j <= end); + } + body + { + return typeof(return)(outer, start + i, start + j); + } + + Range!(const(V)) opIndex() const + { + return typeof(return)(outer, start, end); + } + + Range!(const(V)) opSlice(in size_t i, in size_t j) const + in + { + assert(i <= j); + assert(start + j <= end); + } + body + { + return typeof(return)(outer, start + i, start + j); + } + + static if (isMutable!V) + { + Range opIndexAssign(in ElementType value) + in + { + assert(end <= outer.length); + } + body + { + return outer[start .. end] = value; + } + + Range opSliceAssign(in ElementType value, in size_t i, in size_t j) + in + { + assert(start + j <= end); + } + body + { + return outer[start + i .. start + j] = value; + } + + Range opSliceAssign(in Range!Vector value, in size_t i, in size_t j) + in + { + assert(length == value.length); + } + body + { + return outer[start + i .. start + j] = value; + } + + Range opSliceAssign(in T[] value, in size_t i, in size_t j) + in + { + assert(j - i == value.length); + } + body + { + return outer[start + i .. start + j] = value; + } + } } /** - * Removes all elements from the vector. + * Creates an empty $(D_PSYMBOL Vector). + * + * Params: + * allocator = The allocator should be used for the element + * allocations. + */ + this(IAllocator allocator = theAllocator) + { + this.allocator = allocator; + } + + /** + * Creates a new $(D_PSYMBOL Vector). + * + * Params: + * U = Variadic template for the constructor parameters. + * params = Values to initialize the array with. The last parameter can + * be an allocator, if not, $(D_PSYMBOL theAllocator) is used. + */ + this(U...)(U params) + { + static if (isImplicitlyConvertible!(typeof(params[$ - 1]), IAllocator)) + { + allocator = params[$ - 1]; + auto values = params[0 .. $ - 1]; + } + else + { + allocator = theAllocator; + alias values = params; + } + + resizeArray!T(allocator, vector, values.length); + foreach (i, v; values) + { + vector[i] = v; + } + } + + /** + * Destroys this $(D_PSYMBOL Vector). */ ~this() { dispose(allocator, vector); } + /** + * Removes all elements. + */ + void clear() + { + resizeArray!T(allocator, vector, 0); + } + + /// + unittest + { + auto v = theAllocator.make!(Vector!int)(18, 20, 15); + + v.clear(); + assert(v.length == 0); + } + /** * Returns: Vector length. */ @@ -68,95 +266,134 @@ class Vector(T) return vector.length; } + /// Ditto. + size_t opDollar() const + { + return length; + } + /** - * Expans/shrinks the vector. + * Expands/shrinks the vector. * * Params: * length = New length. */ - @property void length(size_t length) + @property void length(in size_t length) { resizeArray!T(allocator, vector, length); } - /// - unittest - { - auto v = make!(Vector!int)(theAllocator); + /// + unittest + { + auto v = theAllocator.make!(Vector!int); - v.length = 5; - assert(v.length == 5); + v.length = 5; + assert(v.length == 5); - v.length = 7; - assert(v.length == 7); + v.length = 7; + assert(v.length == 7); - v.length = 0; - assert(v.length == 0); - - dispose(theAllocator, v); - } + v.length = 0; + assert(v.length == 0); + } /** * Returns: $(D_KEYWORD true) if the vector is empty. */ @property bool empty() const { - return length == 0; - } - - static if (isFinalizable!T) - { - /** - * Removes an elements from the vector. - * - * Params: - * pos = Element index. - */ - void remove(size_t pos) - { - auto el = vector[pos]; - dispose(allocator, el); - } + return vector.length == 0; } /** - * Assigns a value. Allocates if needed. + * Removes $(D_PARAM howMany) elements from the vector. + * + * This method doesn't fail if it could not remove $(D_PARAM howMany) + * elements. Instead, if $(D_PARAM howMany) is greater than the vector + * length, all elements are removed. * * Params: - * value = Value. + * howMany = How many elements should be removed. * - * Returns: Assigned value. + * Returns: The number of elements removed */ - T opIndexAssign(T value, size_t pos) + size_t removeBack(in size_t howMany) { - if (pos >= length) + immutable toRemove = min(howMany, length); + + static if (hasElaborateDestructor!T) { - resizeArray!T(allocator, vector, pos + 1); + foreach (ref e; vector[$ - toRemove ..$]) + { + allocator.dispose(e); + } } - return vector[pos] = value; + length = length - toRemove; + + return toRemove; } /// unittest { - auto v = make!(Vector!int)(theAllocator); - int[2] values = [5, 15]; + auto v = theAllocator.make!(Vector!int)(5, 18, 17); - assert(v.length == 0); - v[1] = values[0]; - assert(v.length == 2); - v[3] = values[0]; - assert(v.length == 4); - v[4] = values[1]; - assert(v.length == 5); + assert(v.removeBack(0) == 0); + assert(v.removeBack(2) == 2); + assert(v.removeBack(3) == 1); + assert(v.removeBack(3) == 0); - dispose(theAllocator, v); + theAllocator.dispose(v); } /** - * Returns: The value on index $(D_PARAM pos). + * Assigns a value to the element with the index $(D_PARAM pos). + * + * Params: + * value = Value. + * + * Returns: Assigned value. + * + * Precondition: $(D_INLINECODE length > pos) */ - ref T opIndex(in size_t pos) + T opIndexAssign(in T value, in size_t pos) + in + { + assert(length > pos); + } + body + { + return vector[pos] = value; + } + + /// Ditto. + Range!Vector opIndexAssign(in T value) + { + vector[0..$] = value; + return opIndex(); + } + + /// + unittest + { + auto v1 = theAllocator.make!(Vector!int)(12, 1, 7); + + v1[] = 3; + assert(v1[0] == 3); + assert(v1[1] == 3); + assert(v1[2] == 3); + + theAllocator.dispose(v1); + } + + + /** + * Returns: The value on index $(D_PARAM pos). + * + * Precondition: $(D_INLINECODE length > pos) + */ + ref inout(T) opIndex(in size_t pos) inout in { assert(length > pos); @@ -169,19 +406,55 @@ class Vector(T) /// unittest { - auto v = make!(Vector!int)(theAllocator); - int[2] values = [5, 15]; + auto v = theAllocator.make!(Vector!int)(6, 123, 34, 5); - v[1] = values[0]; - assert(v[1] is values[0]); - v[3] = values[0]; - assert(v[3] is values[0]); - v[4] = values[1]; - assert(v[4] is values[1]); - v[0] = values[1]; - assert(v[0] is values[1]); + assert(v[0] == 6); + assert(v[1] == 123); + assert(v[2] == 34); + assert(v[3] == 5); - dispose(theAllocator, v); + theAllocator.dispose(v); + } + + /** + * Comparison for equality. + * + * Params: + * o = The vector to compare with. + * + * Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false) + * otherwise. + */ + override bool opEquals(Object o) + { + auto v = cast(Vector) o; + + return v is null ? super.opEquals(o) : vector == v.vector; + } + + /// + unittest + { + auto v1 = theAllocator.make!(Vector!int); + auto v2 = theAllocator.make!(Vector!int); + + assert(v1 == v2); + + v1.length = 1; + v2.length = 2; + assert(v1 != v2); + + v1.length = 2; + v1[0] = v2[0] = 2; + v1[1] = 3; + v2[1] = 4; + assert(v1 != v2); + + v2[1] = 3; + assert(v1 == v2); + + theAllocator.dispose(v1); + theAllocator.dispose(v2); } /** @@ -190,7 +463,7 @@ class Vector(T) * Params: * dg = $(D_KEYWORD foreach) body. */ - int opApply(int delegate(ref T) dg) + int opApply(scope int delegate(ref T) dg) { int result; @@ -207,7 +480,7 @@ class Vector(T) } /// Ditto. - int opApply(int delegate(ref size_t i, ref T) dg) + int opApply(scope int delegate(ref size_t i, ref T) dg) { int result; @@ -226,183 +499,217 @@ class Vector(T) /// unittest { - auto v = make!(Vector!int)(theAllocator, 1); - int[3] values = [5, 15, 8]; + auto v = theAllocator.make!(Vector!int)(5, 15, 8); - v[0] = values[0]; - v[1] = values[1]; - v[2] = values[2]; - - int i; - foreach (e; v) + size_t i; + foreach (j, ref e; v) { - assert(i != 0 || e is values[0]); - assert(i != 1 || e is values[1]); - assert(i != 2 || e is values[2]); - ++i; + i = j; } + assert(i == 2); foreach (j, e; v) { - assert(j != 0 || e is values[0]); - assert(j != 1 || e is values[1]); - assert(j != 2 || e is values[2]); + assert(j != 0 || e == 5); + assert(j != 1 || e == 15); + assert(j != 2 || e == 8); } - - dispose(theAllocator, v); - } - - /** - * Sets the first element. Allocates if the vector is empty. - * - * Params: - * x = New element. - */ - @property void front(ref T x) - { - this[0] = x; + theAllocator.dispose(v); } /** * Returns: The first element. - */ + * + * Precondition: $(D_INLINECODE length > 0) + */ @property ref inout(T) front() inout - in + in { - assert(!empty); + assert(vector.length > 0); } body { return vector[0]; } - /// - unittest - { - auto v = make!(Vector!int)(theAllocator, 1); - int[2] values = [5, 15]; - - v.front = values[0]; - assert(v.front == 5); - - v.front = values[1]; - assert(v.front == 15); - - dispose(theAllocator, v); - } - - /** - * Move position to the next element. - * - * Returns: $(D_KEYWORD this). - */ - typeof(this) popFront() - in - { - assert(!empty); - } - body - { - vector[0 .. $ - 1] = vector[1..$]; - resizeArray(allocator, vector, length - 1); - return this; - } - /// unittest { - auto v = make!(Vector!int)(theAllocator, 1); - int[2] values = [5, 15]; + auto v = theAllocator.make!(Vector!int)(5); - v[0] = values[0]; - v[1] = values[1]; - assert(v.front is values[0]); - assert(v.length == 2); - v.popFront(); - assert(v.front is values[1]); - assert(v.length == 1); - v.popFront(); - assert(v.empty); + assert(v.front == 5); - dispose(theAllocator, v); - } + v.length = 2; + v[1] = 15; + assert(v.front == 5); - /** - * Sets the last element. Allocates if the vector is empty. - * - * Params: - * x = New element. - */ - @property void back(ref T x) - { - vector[empty ? 0 : $ - 1] = x; + theAllocator.dispose(v); } /** * Returns: The last element. - */ + * + * Precondition: $(D_INLINECODE length > 0) + */ @property ref inout(T) back() inout - in + in { - assert(!empty); + assert(vector.length > 0); } body { return vector[$ - 1]; } - /// - unittest - { - auto v = make!(Vector!int)(theAllocator, 1); - int[2] values = [5, 15]; + /// + unittest + { + auto v = theAllocator.make!(Vector!int)(5); - v.back = values[0]; - assert(v.back == 5); + assert(v.back == 5); - v.back = values[1]; - assert(v.back == 15); + v.length = 2; + v[1] = 15; + assert(v.back == 15); - dispose(theAllocator, v); - } + theAllocator.dispose(v); + } /** - * Move position to the previous element. - * - * Returns: $(D_KEYWORD this). + * Returns: A range that iterates over elements of the container, in + * forward order. */ - typeof(this) popBack() + Range!Vector opIndex() + { + return typeof(return)(this, 0, length); + } + + /// Ditto. + Range!(const Vector) opIndex() const + { + return typeof(return)(this, 0, length); + } + + /// Ditto. + Range!(immutable Vector) opIndex() immutable + { + return typeof(return)(this, 0, length); + } + + /** + * Params: + * i = Slice start. + * j = Slice end. + * + * Returns: A range that iterates over elements of the container from + * index $(D_PARAM i) up to (excluding) index $(D_PARAM j). + * + * Precondition: $(D_INLINECODE i <= j && j <= length) + */ + Range!Vector opSlice(in size_t i, in size_t j) in { - assert(!empty); + assert(i <= j); + assert(j <= length); } body { - resizeArray(allocator, vector, length - 1); - return this; + return typeof(return)(this, i, j); + } + + /// Ditto. + Range!(const Vector) opSlice(in size_t i, in size_t j) const + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, i, j); + } + + /// Ditto. + Range!(immutable Vector) opSlice(in size_t i, in size_t j) immutable + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, i, j); + } + + /** + * Slicing assignment. + * + * Params: + * value = New value. + * i = Slice start. + * j = Slice end. + * + * Returns: Assigned value. + * + * Precondition: $(D_INLINECODE i <= j && j <= length); + * The lenghts of the ranges and slices match. + */ + Range!Vector opSliceAssign(in T value, in size_t i, in size_t j) + in + { + assert(i <= j); + assert(j <= length); + } + body + { + vector[i .. j] = value; + return opSlice(i, j); + } + + /// Ditto. + Range!Vector opSliceAssign(in Range!Vector value, in size_t i, in size_t j) + in + { + assert(j - i == value.length); + } + body + { + vector[i .. j] = value.outer.vector[value.start .. value.end]; + return opSlice(i, j); + } + + /// Ditto. + Range!Vector opSliceAssign(in T[] value, in size_t i, in size_t j) + in + { + assert(j - i == value.length); + } + body + { + vector[i .. j] = value; + return opSlice(i, j); } /// unittest { - auto v = make!(Vector!int)(theAllocator, 1); - int[2] values = [5, 15]; + auto v1 = theAllocator.make!(Vector!int)(3, 3, 3); + auto v2 = theAllocator.make!(Vector!int)(1, 2); - v[0] = values[0]; - v[1] = values[1]; - assert(v.back is values[1]); - assert(v.length == 2); - v.popBack(); - assert(v.back is values[0]); - assert(v.length == 1); - v.popBack(); - assert(v.empty); + v1[0..2] = 286; + assert(v1[0] == 286); + assert(v1[1] == 286); + assert(v1[2] == 3); - dispose(theAllocator, v); + v2[0..$] = v1[1..3]; + assert(v2[0] == 286); + assert(v2[1] == 3); + + theAllocator.dispose(v2); + theAllocator.dispose(v1); } - /// Container. - protected T[] vector; + private T[] vector; private IAllocator allocator; } @@ -410,7 +717,9 @@ class Vector(T) /// unittest { - auto v = make!(Vector!int)(theAllocator); + auto v = theAllocator.make!(Vector!int)(5, 15, 8); - dispose(theAllocator, v); + assert(v.front == 5); + assert(v[1] == 15); + assert(v.back == 8); }