Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

More precise behavior for DList

  • Loading branch information...
commit 054fe9bea5652aa0e3089d279d4df2ae069d9f30 1 parent 413d380
@monarchdodra authored
Showing with 391 additions and 74 deletions.
  1. +391 −74 std/container.d
View
465 std/container.d
@@ -1484,6 +1484,50 @@ unittest
/**
Implements a doubly-linked list.
+
+ $(D DList) uses neither reference nor value semantics. They can be seen as
+ several different handles into an external chain of nodes. Several different
+ $(D DList)s can all reference different points in a same chain.
+
+ $(D DList.Range) is, for all intents and purposes, a DList with range
+ semantics. The $(D DList.Range) has a view directly into the chain itself.
+ It is not tied to its parent $(D DList), and may be used to operate on
+ other lists (that point to the same chain).
+
+ The ONLY operation that can invalidate a $(D DList) or $(D DList.Range), but
+ which will invalidate BOTH, is the $(D remove) operation, if the cut Range
+ overlaps with the boundaries of another DList or DList.Range.
+
+ Example:
+----
+auto a = DList!int([3, 4]); //Create a new chain
+auto b = a; //Point to the same chain
+// (3 - 4)
+assert(a[].equal([3, 4]));
+assert(b[].equal([3, 4]));
+
+b.stableInsertFront(1); //insert before of b
+b.stableInsertBack(5); //insert after of b
+// (2 - (3 - 4) - 5)
+assert(a[].equal([3, 4])); //a is not changed
+assert(b[].equal([1, 3, 4, 5])); // but a is
+
+a.stableInsertFront(2); //insert in front of a, this will insert "inside" the chain
+// (1 - (2 - 3 - 4) - 5)
+assert(a[].equal([2, 3, 4])); //a is modified
+assert(b[].equal([1, 2, 3, 4, 5])); //and so is b;
+
+a.remove(a[]); //remove all the elements of a;
+// (1 - 5)
+assert(a[].empty); //a is empty
+assert(b[].equal([1, 5])); //b has lost some of its elements;
+
+a.insert(2); //insert in a. This will create a new chain
+// (2)
+// (1 - 5)
+assert(a[].equal([2])); //a is empty
+assert(b[].equal([1, 5])); //b has lost some of its elements;
+----
*/
struct DList(T)
{
@@ -1500,8 +1544,8 @@ struct DList(T)
if (n) n._prev = &this;
}
}
- private Node * _first;
- private Node * _last;
+ Node * _first;
+ Node * _last;
/**
Constructor taking a number of nodes
@@ -1517,7 +1561,8 @@ Constructor taking an input range
this(Stuff)(Stuff stuff)
if (isInputRange!Stuff
&& isImplicitlyConvertible!(ElementType!Stuff, T)
- && !is(Stuff == T[]))
+ && !is(Stuff == T[])
+ )
{
insertBack(stuff);
}
@@ -1530,8 +1575,10 @@ elements in $(D rhs).
*/
bool opEquals(ref const DList rhs) const
{
- auto nthis = _first, nrhs = rhs._first;
+ if(_first == rhs._first) return _last == rhs._last;
+ if(_last == rhs._last) return _first == rhs._first;
+ auto nthis = _first, nrhs = rhs._first;
while(true)
{
if (!nthis) return !nrhs;
@@ -1546,15 +1593,35 @@ elements in $(D rhs).
{
private Node * _first;
private Node * _last;
- private this(Node* first, Node* last) { _first = first; _last = last; }
+ private this(Node* first, Node* last)
+ {
+ assert(!!_first == !!_last, "Dlist.Rangethis: Invalid arguments");
+ _first = first; _last = last;
+ }
private this(Node* n) { _first = _last = n; }
- /// Forward range primitives.
- @property bool empty() const { return !_first; }
- @property T front() { return _first._payload; }
+
+ /// Input range primitives.
+ @property const bool empty() {return !_first;}
+
+ /// ditto
+ @property T front()
+ {
+ assert(_first, "Dlist.Range.front: Range is empty");
+ return _first._payload;
+ }
+
+ /// ditto
+ @property void front(T value)
+ {
+ assert(_first, "Dlist.Range.front: Range is empty");
+ _first._payload = value;
+ }
+
+ /// ditto
void popFront()
{
- enforce(_first);
- assert(_last);
+ assert(_first, "Dlist.Range.popFront: Range is empty");
+ assert(_last, "Dlist.Range.popFront: Range is invalid");
if (_first is _last)
{
_first = _last = null;
@@ -1562,15 +1629,32 @@ elements in $(D rhs).
else
{
_first = _first._next;
+ assert(_first, "DList.Range: Range has been cut");
}
}
+
+ /// Forward range primitives.
@property Range save() { return this; }
+
/// Bidirectional range primitives.
- @property T back() { return _last._payload; }
+ @property T back()
+ {
+ assert(_last, "Dlist.Range.back: Range is empty");
+ return _last._payload;
+ }
+
+ /// ditto
+ @property void back(T value)
+ {
+ assert(_last, "Dlist.Range.back: Range is empty");
+ _last._payload = value;
+ }
+
+ /// ditto
void popBack()
{
- enforce(_first);
- assert(_last);
+ assert(_last, "Dlist.Range.popBack: Range is empty");
+ assert(_first, "Dlist.Range.popBack: Range is invalid");
if (_first is _last)
{
_first = _last = null;
@@ -1578,6 +1662,7 @@ elements in $(D rhs).
else
{
_last = _last._prev;
+ assert(_first, "DList.Range: Range has been cut");
}
}
}
@@ -1593,9 +1678,10 @@ elements.
Complexity: $(BIGOH 1)
*/
- @property bool empty() const
+ @property const nothrow
+ bool empty()
{
- return _first is null;
+ return !_first ;
}
/**
@@ -1627,44 +1713,124 @@ Complexity: $(BIGOH 1)
*/
@property T front()
{
- enforce(_first);
+ assert(!empty, "Dlist.Range.front: Range is empty");
return _first._payload;
}
+/**
+Forward to $(D opSlice().front(value)).
+
+Complexity: $(BIGOH 1)
+ */
+ @property void front(T value)
+ {
+ assert(!empty, "Dlist.Range.front: Range is empty");
+ _first._payload = value;
+ }
+
+/**
+Forward to $(D opSlice().back).
+
+Complexity: $(BIGOH 1)
+ */
@property T back()
{
- enforce(_last);
+ assert(!empty, "Dlist.Range.back: Range is empty");
return _last._payload;
}
/**
+Forward to $(D opSlice().back(value)).
+
+Complexity: $(BIGOH 1)
+ */
+ @property void back(T value)
+ {
+ assert(!empty, "Dlist.Range.back: Range is empty");
+ _last._payload = value;
+ }
+
+/**
Returns a new $(D DList) that's the concatenation of $(D this) and its
-argument. $(D opBinaryRight) is only defined if $(D Stuff) does not
-define $(D opBinary).
+argument.
*/
DList opBinary(string op, Stuff)(Stuff rhs)
- if (op == "~" && (is(Stuff == DList) || is(typeof(DList(rhs)))))
+ if (op == "~" && isImplicitlyConvertible!(Stuff, T))
+ {
+ auto ret = this.dup;
+ ret ~= rhs;
+ return ret;
+ }
+
+/// ditto
+ DList opBinary(string op, Stuff)(Stuff rhs)
+ if (op == "~" && isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
{
auto ret = this.dup;
ret ~= rhs;
return ret;
}
- DList opOpassign(string op, Stuff)(Stuff rhs)
- if (op == "~" && is(Stuff == DList))
+/**
+Returns a new $(D DList) that's the concatenation of the argument and $(D this)
+ */
+ DList opBinaryRight(string op, Stuff)(Stuff rhs)
+ if (op == "~" && isImplicitlyConvertible!(Stuff, T))
{
- if (_last) _last._next = rhs._first;
- if (rhs._first) rhs_.first._prev = _last;
+ auto ret = this.dup;
+ return ret.opOpAssignRightPrivate!"~"(rhs);
+ return ret;
}
- DList opOpassign(string op, Stuff)(Stuff rhs)
- if (op == "~" && is(typeof(DList(rhs))))
+/// ditto
+ DList opBinaryRight(string op, Stuff)(Stuff rhs)
+ if (op == "~" && isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
{
- opOpassign!(op)(DList(rhs));
+ auto ret = this.dup;
+ return ret.opOpAssignRightPrivate!"~"(rhs);
+ return ret;
}
/**
-Removes all contents from the $(D DList).
+Appends the contents of stuff into this.
+ */
+ DList opOpAssign(string op, Stuff)(Stuff rhs)
+ if (op == "~" && isImplicitlyConvertible!(Stuff, T))
+ {
+ insertBack(rhs);
+ return this;
+ }
+
+/// ditto
+ DList opOpAssign(string op, Stuff)(Stuff rhs)
+ if (op == "~" && isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+ {
+ insertBack(rhs);
+ return this;
+ }
+
+ //Private implementations helpers for opOpBinaryRight
+ DList opOpAssignRightPrivate(string op, Stuff)(Stuff lhs)
+ if (op == "~" && isImplicitlyConvertible!(Stuff, T))
+ {
+ insertFront(lhs);
+ return this;
+ }
+
+ //Private implementations helpers for opOpBinaryRight
+ DList opOpAssignRightPrivate(string op, Stuff)(Stuff lhs)
+ if (op == "~" && isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+ {
+ this.insertFront(lhs);
+ return this;
+ }
+
+/**
+Removes all contents from the $(D DList) by detaching from the references.
+Other $(D DLists) referencing the same data are left unchanged.
+
+To effectivelly remove the actual contents from all $(D DLists) referencing
+the same data, call $(D remove(opSlice()).
Postcondition: $(D empty)
@@ -1681,6 +1847,8 @@ value convertible to $(D T) or a range of objects convertible to $(D
T). The stable version behaves the same, but guarantees that ranges
iterating over the container are never invalidated.
+Elements are inserted into the chain.
+
Returns: The number of elements inserted
Complexity: $(BIGOH log(n))
@@ -1712,6 +1880,9 @@ Complexity: $(BIGOH log(n))
Picks one value from the front of the container, removes it from the
container, and returns it.
+Elements are not actually removed from the chain, but the $(D DList)'s,
+first/last pointer is advanced.
+
Precondition: $(D !empty)
Returns: The element removed.
@@ -1720,9 +1891,10 @@ Complexity: $(BIGOH 1).
*/
T removeAny()
{
- enforce(!empty);
+ assert(!empty, "DList.removeAny: List is empty");
auto result = move(_last._payload);
_last = _last._prev;
+ if (_last is null) _first = null;
return result;
}
/// ditto
@@ -1733,13 +1905,16 @@ Removes the value at the front/back of the container. The stable version
behaves the same, but guarantees that ranges iterating over the
container are never invalidated.
+Elements are not actually removed from the chain, but the $(D DList)'s,
+first/last pointer is advanced.
+
Precondition: $(D !empty)
Complexity: $(BIGOH 1).
*/
void removeFront()
{
- enforce(_first);
+ assert(!empty, "DList.removeFront: List is empty");
_first = _first._next;
if (_first is null)
{
@@ -1753,7 +1928,7 @@ Complexity: $(BIGOH 1).
/// ditto
void removeBack()
{
- enforce(_last);
+ assert(!empty, "DList.removeBack: List is empty");
_last = _last._prev;
if (_last is null)
{
@@ -1773,6 +1948,9 @@ the effective number of elements removed. The stable version behaves
the same, but guarantees that ranges iterating over the container are
never invalidated.
+Elements are not actually removed from the chain, but the $(D DList)'s,
+first/last pointer is advanced.
+
Returns: The number of elements removed
Complexity: $(BIGOH howMany * log(n)).
@@ -1823,6 +2001,8 @@ convertible to $(D T). The stable version behaves the same, but
guarantees that ranges iterating over the container are never
invalidated.
+Elements are inserted into the chain.
+
Returns: The number of values inserted.
Complexity: $(BIGOH k + m), where $(D k) is the number of elements in
@@ -1834,10 +2014,11 @@ $(D r) and $(D m) is the length of $(D stuff).
Node* n = (r._first) ? r._first : _first;
return insertBeforeNode(n, stuff);
}
+
/// ditto
alias insertBefore stableInsertBefore;
-
+ /// ditto
size_t insertAfter(Stuff)(Range r, Stuff stuff)
{
Node* n = (r._last) ? r._last._next : null;
@@ -1847,57 +2028,71 @@ $(D r) and $(D m) is the length of $(D stuff).
/// ditto
alias insertAfter stableInsertAfter;
- /// Helper: insert $(D stuff) before Node $(D n). If $(D n) is $(D null) then insert at end.
+ // Helper: insert $(D stuff) before Node $(D n). If $(D n) is $(D null) then insert at end.
private size_t insertBeforeNode(Stuff)(Node* n, Stuff stuff)
- if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
+ if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
{
size_t result;
- Node* prev = (n) ? n._prev : _last;
+ if(stuff.empty) return result;
+
+ Node* first;
+ Node* last;
+ //scope block
+ {
+ auto item = stuff.front;
+ stuff.popFront;
+ last = first = new Node(item, null, null);
+ ++result;
+ }
foreach (item; stuff)
{
- if (_first is null)
- {
- prev = _first = new Node(item, null, null);
- }
- else if (n is _first)
+ last = new Node(item, last, null);
+ ++result;
+ }
+
+ //We have created a first-last chain. Now we insert it.
+ if(!_first)
+ {
+ _first = first;
+ _last = last;
+ }
+ else
+ {
+ assert(_last);
+ if(n)
{
- prev = _first = new Node(item, prev, _first);
+ if(n._prev)
+ {
+ n._prev._next = first;
+ first._prev = n._prev;
+ }
+ n._prev = last;
+ last._next = n;
+ if(n is _first)
+ _first = first;
}
else
{
- prev = new Node(item, prev, n);
+ if(_last._next)
+ {
+ _last._next._prev = last;
+ last._next = _last._next;
+ }
+ _last._next = first;
+ first._prev = _last;
+ _last = last;
}
- ++result;
- }
- if (!_last || !n)
- {
- _last = prev;
}
+ assert(_first);
+ assert(_last);
return result;
}
- /// Helper: insert $(D stuff) before Node $(D n). If $(D n) is $(D null) then insert at end.
+ // Helper: insert $(D stuff) before Node $(D n). If $(D n) is $(D null) then insert at end.
private size_t insertBeforeNode(Stuff)(Node* n, Stuff stuff)
- if (isImplicitlyConvertible!(Stuff, T))
+ if (isImplicitlyConvertible!(Stuff, T))
{
- Node* prev = (n !is null) ? n._prev : _last;
- if (_first is null)
- {
- prev = _first = new Node(stuff, null, null);
- }
- else if (n is _first)
- {
- prev = _first = new Node(stuff, prev, _first);
- }
- else
- {
- prev = new Node(stuff, prev, n);
- }
- if (!_last || !n)
- {
- _last = prev;
- }
- return 1;
+ return insertBeforeNode(n, [stuff]);
}
/**
@@ -1906,6 +2101,12 @@ obtained originally from this container. The stable version behaves the
same, but guarantees that ranges iterating over the container are
never invalidated.
+This function actually removes the elements from the chain. This is the
+only function that may invalidate a range, as it cuts the chain of elements:
+*Ranges that contain $(D r) or that are inside $(D r), as well a $(D r)
+itself, is never invalidated.
+*Ranges which partially overlap with $(D r) will be cut, and invalidated.
+
Returns: A range spanning the remaining elements in the container that
initially were right after $(D r).
@@ -1917,25 +2118,52 @@ Complexity: $(BIGOH 1)
{
return r;
}
- enforce(_first);
+ assert(!empty, "DList.remove: Range is bigger than List");
+
+ //Note about the unusual complexity here:
+ //The first and last nodes are not necessarilly the actual last nodes
+ //of the "chain".
+ //If we merelly excise the range from the chain, we can run into odd behavior,
+ //in particlar, when the range's front and/or back coincide with the List's...
+
Node* before = r._first._prev;
- Node* after = r._last._next;
+ Node* after = r._last._next;
+
+ Node* oldFirst = _first;
+ Node* oldLast = _last;
+
if (before)
{
- before._next = after;
+ if (after)
+ {
+ before._next = after;
+ after._prev = before;
+ }
+ if (_first == r._first)
+ _first = oldLast != r._last ? after : null ;
}
else
{
- _first = after;
+ assert(oldFirst == r._first, "Dlist.remove: Range is not part of the list");
+ _first = oldLast != r._last ? after : null ;
}
+
if (after)
{
- after._prev = before;
+ if (before)
+ {
+ after._next = before;
+ before._prev = after;
+ }
+ if (_last == r._last)
+ _last = oldFirst != r._last ? after : null ;
}
else
{
- _last = before;
+ assert(oldLast == r._last, "Dlist.remove: Range is not part of the list");
+ _last = oldFirst != r._first ? before : null ;
}
+
return Range(after, _last);
}
@@ -1962,7 +2190,8 @@ Complexity: $(BIGOH 1)
last = r.source._first;
r.popFront();
}
- return remove(Range(first, last));
+ auto rr = Range(first, last);
+ return remove(rr);
}
/// ditto
@@ -1971,6 +2200,37 @@ Complexity: $(BIGOH 1)
unittest
{
+ auto a = DList!int([3, 4]); //Create a new chain
+ auto b = a; //Point to the same chain
+ // (3 - 4)
+ assert(a[].equal([3, 4]));
+ assert(b[].equal([3, 4]));
+
+ b.stableInsertFront(1); //insert before of b
+ b.stableInsertBack(5); //insert after of b
+ // (2 - (3 - 4) - 5)
+ assert(a[].equal([3, 4])); //a is not changed
+ assert(b[].equal([1, 3, 4, 5])); // but a is
+
+ a.stableInsertFront(2); //insert in front of a, this will insert "inside" the chain
+ // (1 - (2 - 3 - 4) - 5)
+ assert(a[].equal([2, 3, 4])); //a is modified
+ assert(b[].equal([1, 2, 3, 4, 5])); //and so is b;
+
+ a.remove(a[]); //remove all the elements of a;
+ // (1 - 5)
+ assert(a[].empty); //a is empty
+ assert(b[].equal([1, 5])); //b has lost some of its elements;
+
+ a.insert(2); //insert in a. This will create a new chain
+ // (2)
+ // (1 - 5)
+ assert(a[].equal([2])); //a is empty
+ assert(b[].equal([1, 5])); //b has lost some of its elements;
+}
+
+unittest
+{
alias DList!int IntList;
IntList list = IntList([0,1,2,3]);
assert(equal(list[],[0,1,2,3]));
@@ -2058,6 +2318,63 @@ unittest
assert(equal(dl[], ["e", "a", "c", "b", "d"]));
}
+unittest
+{
+ auto d = DList!int([1, 2, 3]);
+ d.front = 5; //test frontAssign
+ assert(d.front == 5);
+ auto r = d[];
+ r.back = 1;
+ assert(r.back == 1);
+}
+
+unittest
+{
+ auto a = DList!int();
+ assert(a.removeFront(10) == 0);
+ a.insert([1, 2, 3]);
+ assert(a.removeFront(10) == 3);
+ assert(a[].empty);
+}
+
+unittest
+{
+ //Verify all flavors of ~
+ auto a = DList!int();
+ auto b = DList!int();
+ auto c = DList!int([1, 2, 3]);
+ auto d = DList!int([4, 5, 6]);
+
+ assert((a ~ b[])[].empty);
+
+ assert((c ~ d[])[].equal([1, 2, 3, 4, 5, 6]));
+ assert(c[].equal([1, 2, 3]));
+ assert(d[].equal([4, 5, 6]));
+
+ assert((c[] ~ d)[].equal([1, 2, 3, 4, 5, 6]));
+ assert(c[].equal([1, 2, 3]));
+ assert(d[].equal([4, 5, 6]));
+
+ a~=c[];
+ assert(a[].equal([1, 2, 3]));
+ assert(c[].equal([1, 2, 3]));
+
+ a~=d[];
+ assert(a[].equal([1, 2, 3, 4, 5, 6]));
+ assert(d[].equal([4, 5, 6]));
+
+ a~=[7, 8, 9];
+ assert(a[].equal([1, 2, 3, 4, 5, 6, 7, 8, 9]));
+
+ //trick test:
+ auto r = c[];
+ c.removeFront();
+ c.removeBack();
+ c~=d[];
+ assert(c[].equal([2, 4, 5, 6]));
+ assert(r.equal([1, 2, 4, 5, 6, 3]));
+}
+
/**
Array type with deterministic control of memory. The memory allocated
for the array is reclaimed as soon as possible; there is no reliance
Please sign in to comment.
Something went wrong with that request. Please try again.