mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 13:10:35 +03:00
1174 lines
28 KiB
D
1174 lines
28 KiB
D
/**
|
|
This module implements a generic doubly-linked list container.
|
|
It can be used as a queue, dequeue or stack.
|
|
|
|
This module is a submodule of $(MREF std, container).
|
|
|
|
Source: $(PHOBOSSRC std/container/dlist.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.dlist;
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.container : DList;
|
|
|
|
auto s = DList!int(1, 2, 3);
|
|
assert(equal(s[], [1, 2, 3]));
|
|
|
|
s.removeFront();
|
|
assert(equal(s[], [2, 3]));
|
|
s.removeBack();
|
|
assert(equal(s[], [2]));
|
|
|
|
s.insertFront([4, 5]);
|
|
assert(equal(s[], [4, 5, 2]));
|
|
s.insertBack([6, 7]);
|
|
assert(equal(s[], [4, 5, 2, 6, 7]));
|
|
|
|
// If you want to apply range operations, simply slice it.
|
|
import std.algorithm.searching : countUntil;
|
|
import std.range : popFrontN, popBackN, walkLength;
|
|
|
|
auto sl = DList!int([1, 2, 3, 4, 5]);
|
|
assert(countUntil(sl[], 2) == 1);
|
|
|
|
auto r = sl[];
|
|
popFrontN(r, 2);
|
|
popBackN(r, 2);
|
|
assert(r.equal([3]));
|
|
assert(walkLength(r) == 1);
|
|
|
|
// DList.Range can be used to remove elements from the list it spans
|
|
auto nl = DList!int([1, 2, 3, 4, 5]);
|
|
for (auto rn = nl[]; !rn.empty;)
|
|
if (rn.front % 2 == 0)
|
|
nl.popFirstOf(rn);
|
|
else
|
|
rn.popFront();
|
|
assert(equal(nl[], [1, 3, 5]));
|
|
auto rs = nl[];
|
|
rs.popFront();
|
|
nl.remove(rs);
|
|
assert(equal(nl[], [1]));
|
|
}
|
|
|
|
import std.range.primitives;
|
|
import std.traits;
|
|
|
|
public import std.container.util;
|
|
|
|
/+
|
|
A DList Node without payload. Used to handle the sentinel node (henceforth "sentinode").
|
|
|
|
Also used for parts of the code that don't depend on the payload type.
|
|
+/
|
|
private struct BaseNode
|
|
{
|
|
private BaseNode* _prev = null;
|
|
private BaseNode* _next = null;
|
|
|
|
/+
|
|
Gets the payload associated with this node.
|
|
This is trusted because all nodes are associated with a payload, even
|
|
the sentinel node.
|
|
It is also not possible to mix Nodes in DLists of different types.
|
|
This function is implemented as a member function here, as UFCS does not
|
|
work with pointers.
|
|
+/
|
|
ref inout(T) getPayload(T)() inout @trusted
|
|
{
|
|
return (cast(inout(DList!T.PayNode)*)&this)._payload;
|
|
}
|
|
|
|
// Helper: Given nodes p and n, connects them.
|
|
static void connect(BaseNode* p, BaseNode* n) @safe nothrow pure
|
|
{
|
|
p._next = n;
|
|
n._prev = p;
|
|
}
|
|
}
|
|
|
|
/+
|
|
The base DList Range. Contains Range primitives that don't depend on payload type.
|
|
+/
|
|
private struct DRange
|
|
{
|
|
@safe unittest
|
|
{
|
|
static assert(isBidirectionalRange!DRange);
|
|
static assert(is(ElementType!DRange == BaseNode*));
|
|
}
|
|
|
|
nothrow @safe @nogc pure:
|
|
private BaseNode* _first;
|
|
private BaseNode* _last;
|
|
|
|
private this(BaseNode* first, BaseNode* last)
|
|
{
|
|
assert((first is null) == (last is null), "Dlist.Range.this: Invalid arguments");
|
|
_first = first;
|
|
_last = last;
|
|
}
|
|
private this(BaseNode* n)
|
|
{
|
|
this(n, n);
|
|
}
|
|
|
|
@property
|
|
bool empty() const scope
|
|
{
|
|
assert((_first is null) == (_last is null), "DList.Range: Invalidated state");
|
|
return !_first;
|
|
}
|
|
|
|
@property BaseNode* front() return scope
|
|
{
|
|
assert(!empty, "DList.Range.front: Range is empty");
|
|
return _first;
|
|
}
|
|
|
|
void popFront() scope
|
|
{
|
|
assert(!empty, "DList.Range.popFront: Range is empty");
|
|
if (_first is _last)
|
|
{
|
|
_first = _last = null;
|
|
}
|
|
else
|
|
{
|
|
assert(_first._next && _first is _first._next._prev, "DList.Range: Invalidated state");
|
|
_first = _first._next;
|
|
}
|
|
}
|
|
|
|
@property BaseNode* back() return scope
|
|
{
|
|
assert(!empty, "DList.Range.front: Range is empty");
|
|
return _last;
|
|
}
|
|
|
|
void popBack() scope
|
|
{
|
|
assert(!empty, "DList.Range.popBack: Range is empty");
|
|
if (_first is _last)
|
|
{
|
|
_first = _last = null;
|
|
}
|
|
else
|
|
{
|
|
assert(_last._prev && _last is _last._prev._next, "DList.Range: Invalidated state");
|
|
_last = _last._prev;
|
|
}
|
|
}
|
|
|
|
/// Forward range primitive.
|
|
@property DRange save() return scope { return this; }
|
|
}
|
|
|
|
/**
|
|
Implements a doubly-linked list.
|
|
|
|
`DList` uses reference semantics.
|
|
*/
|
|
struct DList(T)
|
|
{
|
|
import std.range : Take;
|
|
import std.traits : isMutable;
|
|
|
|
/*
|
|
A Node with a Payload. A PayNode.
|
|
*/
|
|
struct PayNode
|
|
{
|
|
BaseNode _base;
|
|
alias _base this;
|
|
|
|
T _payload = T.init;
|
|
|
|
this (BaseNode _base, T _payload)
|
|
{
|
|
import std.algorithm.mutation : move;
|
|
|
|
this._base = _base;
|
|
this._payload = move(_payload);
|
|
}
|
|
|
|
inout(BaseNode)* asBaseNode() inout @trusted
|
|
{
|
|
return &_base;
|
|
}
|
|
}
|
|
|
|
//The sentinel node
|
|
private BaseNode* _root;
|
|
|
|
private
|
|
{
|
|
//Construct as new PayNode, and returns it as a BaseNode.
|
|
static BaseNode* createNode(Stuff)(auto ref Stuff arg, BaseNode* prev = null, BaseNode* next = null)
|
|
{
|
|
import std.algorithm.mutation : move;
|
|
|
|
static if (isMutable!Stuff)
|
|
return (new PayNode(BaseNode(prev, next), move(arg))).asBaseNode();
|
|
else
|
|
return (new PayNode(BaseNode(prev, next), arg)).asBaseNode();
|
|
}
|
|
|
|
void initialize() nothrow @safe pure
|
|
{
|
|
if (_root) return;
|
|
//Note: We allocate a PayNode for safety reasons.
|
|
_root = (new PayNode()).asBaseNode();
|
|
_root._next = _root._prev = _root;
|
|
}
|
|
ref inout(BaseNode*) _first() @property @safe nothrow pure inout
|
|
{
|
|
assert(_root, "Root pointer must not be null");
|
|
return _root._next;
|
|
}
|
|
ref inout(BaseNode*) _last() @property @safe nothrow pure inout
|
|
{
|
|
assert(_root, "Root pointer must not be null");
|
|
return _root._prev;
|
|
}
|
|
} //end private
|
|
|
|
/**
|
|
Constructor taking a number of nodes
|
|
*/
|
|
this(U)(U[] values...)
|
|
if (isImplicitlyConvertible!(U, T))
|
|
{
|
|
insertBack(values);
|
|
}
|
|
|
|
/**
|
|
Constructor taking an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
|
|
*/
|
|
this(Stuff)(Stuff stuff)
|
|
if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
|
|
{
|
|
insertBack(stuff);
|
|
}
|
|
|
|
/**
|
|
Comparison for equality.
|
|
|
|
Complexity: $(BIGOH min(n, n1)) where `n1` is the number of
|
|
elements in `rhs`.
|
|
*/
|
|
bool opEquals()(ref const DList rhs) const
|
|
if (is(typeof(front == front)))
|
|
{
|
|
const lhs = this;
|
|
const lroot = lhs._root;
|
|
const rroot = rhs._root;
|
|
|
|
if (lroot is rroot) return true;
|
|
if (lroot is null) return rroot is rroot._next;
|
|
if (rroot is null) return lroot is lroot._next;
|
|
|
|
const(BaseNode)* pl = lhs._first;
|
|
const(BaseNode)* pr = rhs._first;
|
|
while (true)
|
|
{
|
|
if (pl is lroot) return pr is rroot;
|
|
if (pr is rroot) return false;
|
|
|
|
// !== because of NaN
|
|
if (!(pl.getPayload!T() == pr.getPayload!T())) return false;
|
|
|
|
pl = pl._next;
|
|
pr = pr._next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Defines the container's primary range, which embodies a bidirectional range.
|
|
*/
|
|
struct Range
|
|
{
|
|
static assert(isBidirectionalRange!Range);
|
|
|
|
DRange _base;
|
|
alias _base this;
|
|
|
|
private this(BaseNode* first, BaseNode* last)
|
|
{
|
|
_base = DRange(first, last);
|
|
}
|
|
private this(BaseNode* n)
|
|
{
|
|
this(n, n);
|
|
}
|
|
|
|
@property ref T front()
|
|
{
|
|
return _base.front.getPayload!T();
|
|
}
|
|
|
|
@property ref T back()
|
|
{
|
|
return _base.back.getPayload!T();
|
|
}
|
|
|
|
//Note: shadows base DRange.save.
|
|
//Necessary for static covariance.
|
|
@property Range save() { return this; }
|
|
}
|
|
|
|
/**
|
|
Property returning `true` if and only if the container has no
|
|
elements.
|
|
|
|
Complexity: $(BIGOH 1)
|
|
*/
|
|
bool empty() @property const nothrow
|
|
{
|
|
return _root is null || _root is _first;
|
|
}
|
|
|
|
/**
|
|
Removes all contents from the `DList`.
|
|
|
|
Postcondition: `empty`
|
|
|
|
Complexity: $(BIGOH 1)
|
|
*/
|
|
void clear()
|
|
{
|
|
//remove actual elements.
|
|
remove(this[]);
|
|
}
|
|
|
|
/**
|
|
Duplicates the container. The elements themselves are not transitively
|
|
duplicated.
|
|
|
|
Complexity: $(BIGOH n).
|
|
*/
|
|
@property DList dup()
|
|
{
|
|
return DList(this[]);
|
|
}
|
|
|
|
/**
|
|
Returns a range that iterates over all elements of the container, in
|
|
forward order.
|
|
|
|
Complexity: $(BIGOH 1)
|
|
*/
|
|
Range opSlice()
|
|
{
|
|
if (empty)
|
|
return Range(null, null);
|
|
else
|
|
return Range(_first, _last);
|
|
}
|
|
|
|
/**
|
|
Forward to `opSlice().front`.
|
|
|
|
Complexity: $(BIGOH 1)
|
|
*/
|
|
@property ref inout(T) front() inout
|
|
{
|
|
assert(!empty, "DList.front: List is empty");
|
|
return _first.getPayload!T();
|
|
}
|
|
|
|
/**
|
|
Forward to `opSlice().back`.
|
|
|
|
Complexity: $(BIGOH 1)
|
|
*/
|
|
@property ref inout(T) back() inout
|
|
{
|
|
assert(!empty, "DList.back: List is empty");
|
|
return _last.getPayload!T();
|
|
}
|
|
|
|
/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
|
|
/+ BEGIN CONCAT FUNCTIONS HERE +/
|
|
/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
|
|
|
|
/**
|
|
Returns a new `DList` that's the concatenation of `this` and its
|
|
argument `rhs`.
|
|
*/
|
|
DList opBinary(string op, Stuff)(Stuff rhs)
|
|
if (op == "~" && is(typeof(insertBack(rhs))))
|
|
{
|
|
auto ret = this.dup;
|
|
ret.insertBack(rhs);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Returns a new `DList` that's the concatenation of the argument `lhs`
|
|
and `this`.
|
|
*/
|
|
DList opBinaryRight(string op, Stuff)(Stuff lhs)
|
|
if (op == "~" && is(typeof(insertFront(lhs))))
|
|
{
|
|
auto ret = this.dup;
|
|
ret.insertFront(lhs);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Appends the contents of the argument `rhs` into `this`.
|
|
*/
|
|
DList opOpAssign(string op, Stuff)(Stuff rhs)
|
|
if (op == "~" && is(typeof(insertBack(rhs))))
|
|
{
|
|
insertBack(rhs);
|
|
return this;
|
|
}
|
|
|
|
/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
|
|
/+ BEGIN INSERT FUNCTIONS HERE +/
|
|
/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
|
|
|
|
/**
|
|
Inserts `stuff` to the front/back of the container. `stuff` can be a
|
|
value convertible to `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.
|
|
|
|
Returns: The number of elements inserted
|
|
|
|
Complexity: $(BIGOH m), where `m` is the length of `stuff`
|
|
*/
|
|
size_t insertFront(Stuff)(Stuff stuff)
|
|
{
|
|
initialize();
|
|
return insertAfterNode(_root, stuff);
|
|
}
|
|
|
|
/// ditto
|
|
size_t insertBack(Stuff)(Stuff stuff)
|
|
{
|
|
initialize();
|
|
return insertBeforeNode(_root, stuff);
|
|
}
|
|
|
|
/// ditto
|
|
alias insert = insertBack;
|
|
|
|
/// ditto
|
|
alias stableInsert = insert;
|
|
|
|
/// ditto
|
|
alias stableInsertFront = insertFront;
|
|
|
|
/// ditto
|
|
alias stableInsertBack = insertBack;
|
|
|
|
/**
|
|
Inserts `stuff` after range `r`, which must be a non-empty range
|
|
previously extracted from this container.
|
|
|
|
`stuff` can be a value convertible to `T` or a range of objects
|
|
convertible to `T`. The stable version behaves the same, but
|
|
guarantees that ranges iterating over the container are never
|
|
invalidated.
|
|
|
|
Returns: The number of values inserted.
|
|
|
|
Complexity: $(BIGOH k + m), where `k` is the number of elements in
|
|
`r` and `m` is the length of `stuff`.
|
|
*/
|
|
size_t insertBefore(Stuff)(Range r, Stuff stuff)
|
|
{
|
|
if (r._first)
|
|
return insertBeforeNode(r._first, stuff);
|
|
else
|
|
{
|
|
initialize();
|
|
return insertAfterNode(_root, stuff);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
alias stableInsertBefore = insertBefore;
|
|
|
|
/// ditto
|
|
size_t insertAfter(Stuff)(Range r, Stuff stuff)
|
|
{
|
|
if (r._last)
|
|
return insertAfterNode(r._last, stuff);
|
|
else
|
|
{
|
|
initialize();
|
|
return insertBeforeNode(_root, stuff);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
alias stableInsertAfter = insertAfter;
|
|
|
|
/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
|
|
/+ BEGIN REMOVE FUNCTIONS HERE +/
|
|
/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/
|
|
|
|
/**
|
|
Picks one value in an unspecified position in the container, removes
|
|
it from the container, and returns it. The stable version behaves the same,
|
|
but guarantees that ranges iterating over the container are never invalidated.
|
|
|
|
Precondition: `!empty`
|
|
|
|
Returns: The element removed.
|
|
|
|
Complexity: $(BIGOH 1).
|
|
*/
|
|
T removeAny()
|
|
{
|
|
import std.algorithm.mutation : move;
|
|
|
|
assert(!empty, "DList.removeAny: List is empty");
|
|
auto result = move(back);
|
|
removeBack();
|
|
return result;
|
|
}
|
|
/// ditto
|
|
alias stableRemoveAny = removeAny;
|
|
|
|
/**
|
|
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.
|
|
|
|
Precondition: `!empty`
|
|
|
|
Complexity: $(BIGOH 1).
|
|
*/
|
|
void removeFront()
|
|
{
|
|
assert(!empty, "DList.removeFront: List is empty");
|
|
assert(_root is _first._prev, "DList: Inconsistent state");
|
|
BaseNode.connect(_root, _first._next);
|
|
}
|
|
|
|
/// ditto
|
|
alias stableRemoveFront = removeFront;
|
|
|
|
/// ditto
|
|
void removeBack()
|
|
{
|
|
assert(!empty, "DList.removeBack: List is empty");
|
|
assert(_last._next is _root, "DList: Inconsistent state");
|
|
BaseNode.connect(_last._prev, _root);
|
|
}
|
|
|
|
/// ditto
|
|
alias stableRemoveBack = removeBack;
|
|
|
|
/**
|
|
Removes `howMany` values at the front or back of the
|
|
container. Unlike the unparameterized versions above, these functions
|
|
do not throw if they could not remove `howMany` elements. Instead,
|
|
if $(D howMany > n), all elements are removed. The returned value is
|
|
the effective number of elements removed. The stable version behaves
|
|
the same, but guarantees that ranges iterating over the container are
|
|
never invalidated.
|
|
|
|
Returns: The number of elements removed
|
|
|
|
Complexity: $(BIGOH howMany).
|
|
*/
|
|
size_t removeFront(size_t howMany)
|
|
{
|
|
if (!_root) return 0;
|
|
size_t result;
|
|
auto p = _first;
|
|
while (p !is _root && result < howMany)
|
|
{
|
|
p = p._next;
|
|
++result;
|
|
}
|
|
BaseNode.connect(_root, p);
|
|
return result;
|
|
}
|
|
|
|
/// ditto
|
|
alias stableRemoveFront = removeFront;
|
|
|
|
/// ditto
|
|
size_t removeBack(size_t howMany)
|
|
{
|
|
if (!_root) return 0;
|
|
size_t result;
|
|
auto p = _last;
|
|
while (p !is _root && result < howMany)
|
|
{
|
|
p = p._prev;
|
|
++result;
|
|
}
|
|
BaseNode.connect(p, _root);
|
|
return result;
|
|
}
|
|
|
|
/// ditto
|
|
alias stableRemoveBack = removeBack;
|
|
|
|
/**
|
|
Removes all elements belonging to `r`, which must be a range
|
|
obtained originally from this container.
|
|
|
|
Returns: A range spanning the remaining elements in the container that
|
|
initially were right after `r`.
|
|
|
|
Complexity: $(BIGOH 1)
|
|
*/
|
|
Range remove(Range r)
|
|
{
|
|
if (r.empty)
|
|
return r;
|
|
|
|
assert(_root !is null, "Cannot remove from an un-initialized List");
|
|
assert(r._first, "Remove: Range is empty");
|
|
|
|
BaseNode.connect(r._first._prev, r._last._next);
|
|
auto after = r._last._next;
|
|
if (after is _root)
|
|
return Range(null, null);
|
|
else
|
|
return Range(after, _last);
|
|
}
|
|
|
|
/// ditto
|
|
Range linearRemove(Range r)
|
|
{
|
|
return remove(r);
|
|
}
|
|
|
|
/// ditto
|
|
alias stableRemove = remove;
|
|
|
|
/**
|
|
Removes first element of `r`, wich must be a range obtained originally
|
|
from this container, from both DList instance and range `r`.
|
|
|
|
Compexity: $(BIGOH 1)
|
|
*/
|
|
void popFirstOf(ref Range r)
|
|
{
|
|
assert(_root !is null, "Cannot remove from an un-initialized List");
|
|
assert(r._first, "popFirstOf: Range is empty");
|
|
auto prev = r._first._prev;
|
|
auto next = r._first._next;
|
|
r.popFront();
|
|
BaseNode.connect(prev, next);
|
|
}
|
|
|
|
/**
|
|
Removes last element of `r`, wich must be a range obtained originally
|
|
from this container, from both DList instance and range `r`.
|
|
|
|
Compexity: $(BIGOH 1)
|
|
*/
|
|
void popLastOf(ref Range r)
|
|
{
|
|
assert(_root !is null, "Cannot remove from an un-initialized List");
|
|
assert(r._first, "popLastOf: Range is empty");
|
|
auto prev = r._last._prev;
|
|
auto next = r._last._next;
|
|
r.popBack();
|
|
BaseNode.connect(prev, next);
|
|
}
|
|
|
|
/**
|
|
`linearRemove` functions as `remove`, but also accepts ranges that are
|
|
result the of a `take` operation. This is a convenient way to remove a
|
|
fixed amount of elements from the range.
|
|
|
|
Complexity: $(BIGOH r.walkLength)
|
|
*/
|
|
Range linearRemove(Take!Range r)
|
|
{
|
|
assert(_root !is null, "Cannot remove from an un-initialized List");
|
|
assert(r.source._first, "Remove: Range is empty");
|
|
|
|
BaseNode* first = r.source._first;
|
|
BaseNode* last = null;
|
|
do
|
|
{
|
|
last = r.source._first;
|
|
r.popFront();
|
|
} while ( !r.empty );
|
|
|
|
return remove(Range(first, last));
|
|
}
|
|
|
|
/// ditto
|
|
alias stableLinearRemove = linearRemove;
|
|
|
|
/**
|
|
Removes the first occurence of an element from the list in linear time.
|
|
|
|
Returns: True if the element existed and was successfully removed, false otherwise.
|
|
|
|
Params:
|
|
value = value of the node to be removed
|
|
|
|
Complexity: $(BIGOH n)
|
|
*/
|
|
bool linearRemoveElement(T value)
|
|
{
|
|
import std.algorithm.mutation : move;
|
|
|
|
auto n1 = findNodeByValue(_root, move(value));
|
|
if (n1)
|
|
{
|
|
auto n2 = n1._next._next;
|
|
BaseNode.connect(n1, n2);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
BaseNode* findNodeByValue(BaseNode* n, T value)
|
|
{
|
|
if (!n) return null;
|
|
auto ahead = n._next;
|
|
while (ahead && ahead.getPayload!T() != value)
|
|
{
|
|
n = ahead;
|
|
ahead = n._next;
|
|
if (ahead == _last._next) return null;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Helper: Inserts stuff before the node n.
|
|
size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff)
|
|
if (isImplicitlyConvertible!(Stuff, T))
|
|
{
|
|
auto p = createNode(stuff, n._prev, n);
|
|
n._prev._next = p;
|
|
n._prev = p;
|
|
return 1;
|
|
}
|
|
// ditto
|
|
size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff)
|
|
if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
|
|
{
|
|
if (stuff.empty) return 0;
|
|
size_t result;
|
|
Range r = createRange(stuff, result);
|
|
BaseNode.connect(n._prev, r._first);
|
|
BaseNode.connect(r._last, n);
|
|
return result;
|
|
}
|
|
|
|
// Helper: Inserts stuff after the node n.
|
|
size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff)
|
|
if (isImplicitlyConvertible!(Stuff, T))
|
|
{
|
|
auto p = createNode(stuff, n, n._next);
|
|
n._next._prev = p;
|
|
n._next = p;
|
|
return 1;
|
|
}
|
|
// ditto
|
|
size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff)
|
|
if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
|
|
{
|
|
if (stuff.empty) return 0;
|
|
size_t result;
|
|
Range r = createRange(stuff, result);
|
|
BaseNode.connect(r._last, n._next);
|
|
BaseNode.connect(n, r._first);
|
|
return result;
|
|
}
|
|
|
|
// Helper: Creates a chain of nodes from the range stuff.
|
|
Range createRange(Stuff)(ref Stuff stuff, ref size_t result)
|
|
{
|
|
BaseNode* first = createNode(stuff.front);
|
|
BaseNode* last = first;
|
|
++result;
|
|
for ( stuff.popFront() ; !stuff.empty ; stuff.popFront() )
|
|
{
|
|
auto p = createNode(stuff.front, last);
|
|
last = last._next = p;
|
|
++result;
|
|
}
|
|
return Range(first, last);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto e = DList!int();
|
|
auto b = e.linearRemoveElement(1);
|
|
assert(b == false);
|
|
assert(e.empty());
|
|
auto a = DList!int(-1, 1, 2, 1, 3, 4);
|
|
b = a.linearRemoveElement(1);
|
|
assert(equal(a[], [-1, 2, 1, 3, 4]));
|
|
assert(b == true);
|
|
b = a.linearRemoveElement(-1);
|
|
assert(b == true);
|
|
assert(equal(a[], [2, 1, 3, 4]));
|
|
b = a.linearRemoveElement(1);
|
|
assert(b == true);
|
|
assert(equal(a[], [2, 3, 4]));
|
|
b = a.linearRemoveElement(2);
|
|
assert(b == true);
|
|
b = a.linearRemoveElement(20);
|
|
assert(b == false);
|
|
assert(equal(a[], [3, 4]));
|
|
b = a.linearRemoveElement(4);
|
|
assert(b == true);
|
|
assert(equal(a[], [3]));
|
|
b = a.linearRemoveElement(3);
|
|
assert(b == true);
|
|
assert(a.empty());
|
|
a.linearRemoveElement(3);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
//Tests construction signatures
|
|
alias IntList = DList!int;
|
|
auto a0 = IntList();
|
|
auto a1 = IntList(0);
|
|
auto a2 = IntList(0, 1);
|
|
auto a3 = IntList([0]);
|
|
auto a4 = IntList([0, 1]);
|
|
|
|
assert(a0[].empty);
|
|
assert(equal(a1[], [0]));
|
|
assert(equal(a2[], [0, 1]));
|
|
assert(equal(a3[], [0]));
|
|
assert(equal(a4[], [0, 1]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
alias IntList = DList!int;
|
|
IntList list = IntList([0,1,2,3]);
|
|
assert(equal(list[],[0,1,2,3]));
|
|
list.insertBack([4,5,6,7]);
|
|
assert(equal(list[],[0,1,2,3,4,5,6,7]));
|
|
|
|
list = IntList();
|
|
list.insertFront([0,1,2,3]);
|
|
assert(equal(list[],[0,1,2,3]));
|
|
list.insertFront([4,5,6,7]);
|
|
assert(equal(list[],[4,5,6,7,0,1,2,3]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : take;
|
|
|
|
alias IntList = DList!int;
|
|
IntList list = IntList([0,1,2,3]);
|
|
auto range = list[];
|
|
for ( ; !range.empty; range.popFront())
|
|
{
|
|
int item = range.front;
|
|
if (item == 2)
|
|
{
|
|
list.stableLinearRemove(take(range, 1));
|
|
break;
|
|
}
|
|
}
|
|
assert(equal(list[],[0,1,3]));
|
|
|
|
list = IntList([0,1,2,3]);
|
|
range = list[];
|
|
for ( ; !range.empty; range.popFront())
|
|
{
|
|
int item = range.front;
|
|
if (item == 2)
|
|
{
|
|
list.stableLinearRemove(take(range,2));
|
|
break;
|
|
}
|
|
}
|
|
assert(equal(list[],[0,1]));
|
|
|
|
list = IntList([0,1,2,3]);
|
|
range = list[];
|
|
for ( ; !range.empty; range.popFront())
|
|
{
|
|
int item = range.front;
|
|
if (item == 0)
|
|
{
|
|
list.stableLinearRemove(take(range,2));
|
|
break;
|
|
}
|
|
}
|
|
assert(equal(list[],[2,3]));
|
|
|
|
list = IntList([0,1,2,3]);
|
|
range = list[];
|
|
for ( ; !range.empty; range.popFront())
|
|
{
|
|
int item = range.front;
|
|
if (item == 1)
|
|
{
|
|
list.stableLinearRemove(take(range,2));
|
|
break;
|
|
}
|
|
}
|
|
assert(equal(list[],[0,3]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto dl = DList!int([1, 2, 3, 4, 5]);
|
|
auto r = dl[];
|
|
r.popFront();
|
|
dl.popFirstOf(r);
|
|
assert(equal(dl[], [1, 3, 4, 5]));
|
|
assert(equal(r, [3, 4, 5]));
|
|
r.popBack();
|
|
dl.popLastOf(r);
|
|
assert(equal(dl[], [1, 3, 5]));
|
|
assert(equal(r, [3]));
|
|
dl = DList!int([0]);
|
|
r = dl[];
|
|
dl.popFirstOf(r);
|
|
assert(dl.empty);
|
|
dl = DList!int([0]);
|
|
r = dl[];
|
|
dl.popLastOf(r);
|
|
assert(dl.empty);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto dl = DList!string(["a", "b", "d"]);
|
|
dl.insertAfter(dl[], "e"); // insert at the end
|
|
assert(equal(dl[], ["a", "b", "d", "e"]));
|
|
auto dlr = dl[];
|
|
dlr.popBack(); dlr.popBack();
|
|
dl.insertAfter(dlr, "c"); // insert after "b"
|
|
assert(equal(dl[], ["a", "b", "c", "d", "e"]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto dl = DList!string(["a", "b", "d"]);
|
|
dl.insertBefore(dl[], "e"); // insert at the front
|
|
assert(equal(dl[], ["e", "a", "b", "d"]));
|
|
auto dlr = dl[];
|
|
dlr.popFront(); dlr.popFront();
|
|
dl.insertBefore(dlr, "c"); // insert before "b"
|
|
assert(equal(dl[], ["e", "a", "c", "b", "d"]));
|
|
}
|
|
|
|
@safe 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);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=8895
|
|
@safe unittest
|
|
{
|
|
auto a = make!(DList!int)(1,2,3,4);
|
|
auto b = make!(DList!int)(1,2,3,4);
|
|
auto c = make!(DList!int)(1,2,3,5);
|
|
auto d = make!(DList!int)(1,2,3,4,5);
|
|
assert(a == b); // this better terminate!
|
|
assert(!(a == c));
|
|
assert(!(a == d));
|
|
}
|
|
|
|
@safe 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);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto a = DList!int();
|
|
assert(a.removeFront(10) == 0);
|
|
a.insert([1, 2, 3]);
|
|
assert(a.removeFront(10) == 3);
|
|
assert(a[].empty);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
//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();
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=8905
|
|
auto a = DList!int([1, 2, 3, 4]);
|
|
auto r = a[];
|
|
a.stableRemoveBack();
|
|
a.stableInsertBack(7);
|
|
assert(a[].equal([1, 2, 3, 7]));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=12566
|
|
@safe unittest
|
|
{
|
|
auto dl2 = DList!int([2,7]);
|
|
dl2.removeFront();
|
|
assert(dl2[].walkLength == 1);
|
|
dl2.removeBack();
|
|
assert(dl2.empty, "not empty?!");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13076
|
|
@safe unittest
|
|
{
|
|
DList!int list;
|
|
assert(list.empty);
|
|
list.clear();
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13425
|
|
@safe unittest
|
|
{
|
|
import std.range : drop, take;
|
|
auto list = DList!int([1,2,3,4,5]);
|
|
auto r = list[].drop(4); // r is a view of the last element of list
|
|
assert(r.front == 5 && r.walkLength == 1);
|
|
r = list.linearRemove(r.take(1));
|
|
assert(r.empty); // fails
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14300
|
|
@safe unittest
|
|
{
|
|
interface ITest {}
|
|
static class Test : ITest {}
|
|
|
|
DList!ITest().insertBack(new Test());
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15263
|
|
@safe unittest
|
|
{
|
|
import std.range : iota;
|
|
auto a = DList!int();
|
|
a.insertFront(iota(0, 5)); // can insert range with non-ref front
|
|
assert(a.front == 0 && a.back == 4);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=22147
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.mutation : move;
|
|
|
|
static struct Item
|
|
{
|
|
@disable this(this);
|
|
|
|
int x;
|
|
}
|
|
|
|
auto list = DList!Item();
|
|
list.insertFront(Item(1));
|
|
assert(list[].walkLength == 1);
|
|
assert(list.front.x == 1);
|
|
auto item = list.moveFront;
|
|
item.x = 2;
|
|
list.front = move(item);
|
|
assert(list.front.x == 2);
|
|
list.removeFront();
|
|
assert(list[].walkLength == 0);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=24637
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
struct A
|
|
{
|
|
int c;
|
|
}
|
|
|
|
DList!A B;
|
|
B.insert(A(1));
|
|
assert(B[].equal([A(1)]));
|
|
|
|
const a = A(3);
|
|
B.insert(a);
|
|
assert(B[].equal([A(1), A(3)]));
|
|
}
|