/** \file list.d * \brief A doubly-linked list and a circular doubly-linked list. * * Written by Ben Hinkle and released to the public domain, as * explained at http://creativecommons.org/licenses/publicdomain * Email comments and bug reports to ben.hinkle@gmail.com * * revision 2.7.1 */ module mintl.list; private import mintl.share; // for ~ and ~= private import mintl.sorting; import mintl.mem; // shared data structure between List and CircularList private struct DNode(Value) { DNode* next, prev; Value data; Value* sortLookup(){return &data;} } /** Template for member functions common to List and CircularList */ template MCommonList(alias tail_, Container ) { /** Test if a container is empty. */ bool isEmpty() { return head_ is null; } /** Get the length of list. The computation can be O(n) but the * result is cached and the actively updated until another list * of unknown length is concatenated or removed. */ size_t length() { if (length_ == 0 && head_) { Container.Node* n = head_; length_ = 1; Container.Node* end = tail_; while (n !is end) { length_++; n = n.next; } } return length_; } // helper function to check if the index is legal void boundsCheck(size_t n) { version (MinTLNoIndexChecking) { } else { if (!(n == 0 && this.head_) && (n >= length_ && n >= this.length)) { throw new IndexOutOfBoundsException(); } } } // Internal function to get the nth item of the list. package Container.Node* getNode(size_t n) { boundsCheck(n); Container.Node* v; if (n <= length_/2) { v = head_; while (n--) { v = v.next; } } else { n = length_-n-1; v = tail_; while (n--) { v = v.prev; } } return v; } /** Get the nth item in the list from head. The operation is O(N(n)) * where N(x) is the distance from x to either end of list. * Indexing out of bounds throws an IndexOutOfBoundsException unless * version=MinTLNoIndexChecking is set. */ Container.ValueType opIndex(size_t n) { return getNode(n).data; } static if (!Container.isReadOnly) { /** Get a pointer to the nth item in the list from head. The * operation is O(N(n)) where N(x) is the distance from x to either * end of list. Indexing out of bounds throws an * IndexOutOfBoundsException unless version=MinTLNoIndexChecking is * set. */ Container.ValueType* lookup(size_t n) { return &getNode(n).data; } /** Set the nth item in the list from head. The operation is O(N(n)) * where N(x) is the distance from x to either end of list. * Indexing out of bounds throws an IndexOutOfBoundsException unless * version=MinTLNoIndexChecking is set. */ void opIndexAssign(Container.ValueType val, size_t n) { getNode(n).data = val; } /** Reverse a list in-place. The operation is O(n) where n is * length of the list. */ Container reverse() { if (this.isEmpty) return *this; Node* i = head_; Node* j = tail_; TypeInfo ti = typeid(Container.ValueType); while (i !is j && i.next !is j) { ti.swap(&i.data,&j.data); i = i.next; j = j.prev; } ti.swap(&i.data,&j.data); return *this; } } // !isReadOnly /** Copies the list contents to an array */ Container.ValueType[] values() { Container.ValueType[] buffer = new Container.ValueType[this.length]; foreach(size_t n, Container.ValueType val; *this) { buffer[n] = val; } return buffer; } /** Test for equality of two lists. The operation is O(n) where n * is length of the list. */ int opEquals(Container c) { if (length_ && c.length_ && length_ != c.length_) return 0; Container.Node* i = head_; Container.Node* j = c.head_; Container.Node* t = tail_; Container.Node* ct = c.tail_; TypeInfo ti = typeid(Container.ValueType); while (i !is null && j !is null) { if (!ti.equals(&i.data,&j.data)) return 0; if (i !is t && j !is ct) return 1; i = i.next; j = j.next; } return (i is null && j is null); } /** Compare two lists. */ int opCmp(Container c) { Container.Node* i = head_; Container.Node* j = c.head_; Container.Node* t = tail_; Container.Node* ct = c.tail_; TypeInfo ti = typeid(Container.ValueType); while (i !is null && j !is null) { int cmp = ti.compare(&i.data,&j.data); if (cmp) return cmp; if (i !is t && j !is ct) return 0; i = i.next; j = j.next; } if (i is null && j is null) return 0; else return (i is null) ? -1 : 1; } /** Create a sub-list from index a to b (exclusive). The operation is * O(max(N(a),N(b))) where N(x) is distance from x to either end of * the target list. */ Container.SliceType opSlice(size_t a, size_t b) { Container.SliceType res; res.length_ = b-a; if (res.length_ > 0) { res.head_ = getNode(a); if (this.length_ - b > b-a){ Container.Node* v = res.head_; b = b-a-1; while (b--) v = v.next; res.tail_ = v; } else { res.tail_ = getNode(b-1); } } return res; } /** Create a sub-list from the head of a to the tail of b (inclusive). */ Container.SliceType opSlice(Container.SliceType a, Container.SliceType b) { if (a.isEmpty) return b; if (b.isEmpty) return a; Container.SliceType res; Container.Node* i = a.head_; res.head_ = i; res.tail_ = b.tail_; res.length_ = 0; // flag indicating unknown length return res; } /** Iterates over the list from head to tail calling delegate to * perform an action. The value is passed to the delegate. */ int opApplyNoKeyStep(int delegate(inout Container.ValueType x) dg, int step = 1){ int dg2(inout size_t count, inout Container.ValueType val) { return dg(val); } return opApplyWithKeyStep(&dg2,step); } /** Iterates over the list from head to tail calling delegate to * perform an action. The index from 0 and the value are passed * to the delegate. */ int opApplyWithKeyStep(int delegate(inout size_t n, inout Container.ValueType x) dg, int step = 1){ Container.Node* i = step>0 ? head_ : tail_; Container.Node* end = step>0 ? tail_ : head_; int res = 0; size_t n = step>0 ? 0 : this.length-1; while (i !is null) { res = dg(n, i.data); if (res || i is end) break; n += step; i = step>0 ? i.next : i.prev; } return res; } /** Iterates over the list from head to tail calling delegate to * perform an action. A one-item sub-list is passed to the delegate. */ int opApplyIterStep(int delegate(inout Container.SliceType n) dg, int step = 1){ Container.Node* i = step>0 ? head_ : tail_; Container.Node* end = step>0 ? tail_ : head_; int res = 0; Container.SliceType n; n.length_ = 1; while (i !is null) { n.head_ = n.tail_ = i; res = dg(n); if (res || i is end) break; i = step>0 ? i.next : i.prev; } return res; } } /** \class List * \brief A doubly-linked list. * * A List!(Value) is a linked list of data of type Value. A list is * similar to a dynamic array except accessing an element in the * middle of the list is O(n) and appending to the front or back is * O(1). Any operation that is not constant-time will explicitly have * the performance behavior documented. * * The optional ReadOnly parameter List!(Value,ReadOnly) forbids * operations that modify the container. The readonly() property returns * a ReadOnly view of the container. * * The optional allocator parameter List!(Value,false,Allocator) is used * to allocate and free memory. The GC is the default allocator. */ struct List(Value, bit ReadOnly = false, Alloc = GCAllocator) { alias List ContainerType; alias List SliceType; alias Value ValueType; alias size_t IndexType; alias Value SortType; alias DNode!(Value) Node; alias ReadOnly isReadOnly; const int NodeAllocationBlockSize = 10; // allocate 10 nodes at a time /* length 0 means length is unknown. An empty list is indicated by * a null head. The tail can be non-null in order to maintain the * cached nodes. */ invariant { assert( length_ == 0 || head_ !is null ); } // private bug private { size_t length_; Node* head_; Node* tail_; // } /** Get a ReadOnly view of the container */ .List!(Value, true, Alloc) readonly() { .List!(Value, true, Alloc) res; res = *cast(typeof(&res))this; return res; } /** Get a read-write view of the container */ .List!(Value, false, Alloc) readwrite() { .List!(Value, false, Alloc) res; res = *cast(typeof(&res))this; return res; } static if (!ReadOnly) { /** Appends an item to the tail of the list. If the target list is * a sub-list call addAfter instead of addTail to insert an item * after a sub-list. */ void addTail(Value v) { if (head_ is null && tail_ !is null) { // empty list but with cache available head_ = tail_; tail_.data = v; length_ = 1; } else if (tail_ is null || tail_.next is null) { if (head_ is null || head_.prev is null) { // no available nodes so allocate a new one List val; val.head_ = val.tail_ = newNode(); val.length_ = 1; val.head_.data = v; addTail(val); } else { // grab available node from front Node* t = head_.prev; if (t.prev !is null) t.prev.next = head_; head_.prev = t.prev; t.prev = tail_; t.next = null; tail_.next = t; tail_ = t; tail_.data = v; if (length_) length_++; } } else { // grab available node from end tail_.next.prev = tail_; tail_ = tail_.next; tail_.data = v; if (length_) length_++; } } /** Appends a list to the tail of the target list. If the target * list is a sub-list call addAfter instead of addTail to insert * another list after a sub-list. */ void addTail(List v) { if (v.isEmpty) return; if (tail_ !is null) tail_.next = v.head_; v.head_.prev = tail_; if (this.isEmpty) length_ = v.length_; else length_ = increaseLength(length_, v.length_); tail_ = v.tail_; if (head_ is null) head_ = v.head_; } mixin MListCatOperators!(List); /** Clear all contents. */ void clear() { static if (is(Alloc == GCAllocator)) { } else { trim(); Node* i = head_; while (i !is null) { Node* next = i.next; Alloc.gcFree(i); i = next; } } *this = List.init; } /** Set the value of one-item slice (more generally the head value). */ void value(Value newValue) { head_.data = newValue; } // Helper function for take and remove private Node* takeTailHelper() { if (this.isEmpty) throw new IndexOutOfBoundsException(); Node* v = tail_; if (head_ && head_ is tail_) { head_ = null; } else { tail_ = tail_.prev; } return v; } /** Removes and returns the tail item of the list. The node that * contained the item may be reused in future additions to the * list. To prevent the node from being reused call trim or * call remove with a sublist containing the last item. If * the target list is empty an IndexOutOfBoundsException is thrown * unless version=MinTLNoIndexChecking is set. */ Value takeTail() { Node* v = takeTailHelper(); Value data = v.data; v.data = Value.init; if (length_) length_--; return data; } /** Removes the tail item of the list. */ void removeTail() { Node* v = takeTailHelper(); v.data = Value.init; if (length_) length_--; } /** Prepends an item to the head of the target list. If the target * list is a sub-list call addBefore instead of addHead to insert an * item before a sub-list. */ void addHead(Value v) { if (head_ is null && tail_ !is null) { // empty list but with cache available head_ = tail_; tail_.data = v; length_ = 1; } else if (head_ is null || head_.prev is null) { if (tail_ is null || tail_.next is null) { // no available nodes so allocate a new one List val; val.head_ = val.tail_ = newNode(); val.length_ = 1; val.head_.data = v; addHead(val); } else { // grab available node from end Node* t = tail_.next; if (t.next !is null) t.next.prev = tail_; tail_.next = t.next; t.next = head_; t.prev = null; head_.prev = t; head_ = t; head_.data = v; if (length_) length_++; } } else { // grab available node from front head_.prev.next = head_; head_ = head_.prev; head_.data = v; if (length_) length_++; } } /** Prepends a list to the head of the target list. If the target * list is a sub-list call addBefore instead of addHead to insert a * list before a sub-list. */ void addHead(List v) { if (v.isEmpty) return; if (head_ !is null) head_.prev = v.tail_; v.tail_.next = head_; if (this.isEmpty) length_ = v.length_; else length_ = increaseLength(length_, v.length_); head_ = v.head_; if (tail_ is null) tail_ = v.tail_; } // Helper function for take and remove private Node* takeHeadHelper() { if (this.isEmpty) throw new IndexOutOfBoundsException(); Node* v = head_; if (head_ && head_ is tail_) { head_ = null; } else { head_ = head_.next; } return v; } /** Removes and returns the head item of the list. The node that * contained the item may be reused in future additions to the * list. If the target list is empty an IndexOutOfBoundsException is * thrown unless version=MinTLNoIndexChecking is set. */ Value takeHead() { Node* v = takeHeadHelper(); Value data = v.data; v.data = Value.init; if (length_) length_--; return data; } /** Removes the head item of the list. */ void removeHead() { Node* v = takeHeadHelper(); v.data = Value.init; if (length_) length_--; } /** Insert a list before a sub-list. */ void addBefore(List subv, List v) { if (v.isEmpty) return; if (subv.isEmpty) throw new IndexOutOfBoundsException(); Node* t = subv.head_; if (t.prev !is null) { t.prev.next = v.head_; } v.head_.prev = t.prev; v.tail_.next = t; t.prev = v.tail_; if (t is head_) head_ = v.head_; length_ = increaseLength(length_, v.length_); } /** Insert a list after a sub-list. */ void addAfter(List subv, List v) { if (v.isEmpty) return; if (subv.isEmpty) throw new IndexOutOfBoundsException(); Node* t = subv.tail_; if (t.next !is null) { t.next.prev = v.tail_; } v.tail_.next = t.next; v.head_.prev = t; t.next = v.head_; if (t is tail_) tail_ = v.tail_; length_ = increaseLength(length_, v.length_); } /** Removes a sub-list from the list entirely. */ void remove(List sublist) { if (sublist.isEmpty) return; Node* h = sublist.head_; Node* t = sublist.tail_; if (h is head_ && t is tail_) { head_ = tail_ = null; length_ = 0; return; } Node* hp = h.prev; Node* tn = t.next; if (hp !is null) hp.next = tn; if (tn !is null) tn.prev = hp; if (h is head_) head_ = tn; if (t is tail_) tail_ = hp; length_ = decreaseLength(length_, sublist.length_); } /** Removes an item from the list if present. */ void remove(size_t index) { List item = opSlice(index, index+1); remove(item); } /** Removes an item and return the value if any. */ Value take(size_t index) { List item = opSlice(index, index+1); remove(item); Value val = item[0]; item.clear(); return val; } /** Trims off extra nodes that are not actively being used by the * list but are available for recyling for future add operations. * This function should be called after calling remove and * there are other list or pointer references to the removed item. */ void trim() { if (!this.isEmpty) { Node* i; if (tail_.next) { i = tail_.next; i.prev = null; tail_.next = null; static if (is(Alloc == GCAllocator)) { } else { while (i !is null) { Node* next = i.next; Alloc.gcFree(i); i = next; } } } if (head_.prev) { i = head_.prev; i.next = null; head_.prev = null; static if (is(Alloc == GCAllocator)) { } else { while (i !is null) { Node* prev = i.prev; Alloc.gcFree(i); i = prev; } } } } } } // !ReadOnly /** Duplicates a list. The operation is O(n) where n is length of * the list. */ List dup() { .List!(Value,false,Alloc) res; foreach(ValueType val; *this) { res ~= val; } static if (ReadOnly) { return res.readonly; } else { return res; } } /** Move a sub-list towards the head or tail by n items. If n is * negative the sub-list moves towards the head. A positive end is * the tail, negative the head and 0 is both. By default moves to * the next item. */ void next(int n = 1, int end = 0) { if (length_) length_ -= n*end; while (n-- > 0) { if (end <= 0) head_ = head_.next; if (end >= 0) tail_ = tail_.next; } while (++n < 0) { if (end <= 0) head_ = head_.prev; if (end >= 0) tail_ = tail_.prev; } } /** Get the length of list. The computation can be O(n) but the * result is cached and the actively updated until another list * of unknown length is concatenated. */ size_t length() { if (length_ == 0 && !this.isEmpty) { Node* n = head_; length_ = 1; while (n !is tail_) { length_++; n = n.next; } } return length_; } /** Create a one-item slice of the head. */ List head() { List res; res.length_ = head_? 1 : 0; res.head_ = res.tail_ = head_; return res; } /** Create a one-item slice of the tail. */ List tail() { List res; res.length_ = tail_? 1 : 0; res.head_ = res.tail_ = tail_; return res; } /** Get the value of one-item slice (more generally the head value). * Useful for expressions like x.tail.value or x.head.value. */ Value value() { return head_.data; } /** Iterate backwards over the list (from tail to head). This * should be called as the iteration parameter in a * foreach statement */ ListReverseIter!(Value,ReadOnly,Alloc) backwards() { ListReverseIter!(Value,ReadOnly,Alloc) res; res.list = this; return res; } /** Helper functions for opApply */ mixin MOpApplyImpl!(ContainerType); alias opApplyNoKey opApply; alias opApplyWithKey opApply; alias opApplyIter opApply; private Node* newNode() { static if (is(Alloc == GCAllocator)) { // allocate a block of nodes and return pointer to first one Node[] block = new Node[NodeAllocationBlockSize]; for (int k=1; kadd and remove functions and * slices can be moved forward around the list indefinitely. A CircularList * also has a smaller memory footprint since it requires only one * pointer for the head instead of two pointers for a tail and head. * * The optional ReadOnly parameter CircularList!(Value,ReadOnly) forbids * operations that modify the container. The readonly() property returns * a ReadOnly view of the container. * * The optional allocator parameter CircularList!(Value,false,Allocator) is used * to allocate and free memory. The GC is the default allocator. */ struct CircularList(Value, bit ReadOnly = false, Alloc = GCAllocator) { alias CircularList ContainerType; alias List!(Value,ReadOnly,Alloc) SliceType; alias Value ValueType; alias size_t IndexType; alias DNode!(Value) Node; alias ReadOnly isReadOnly; private { size_t length_; Node* head_; } /* length 0 means length is unknown. */ invariant { assert( length_ == 0 || head_ !is null ); } /** Return the circular list as a non-circular List. * \return the list as a List */ SliceType toList() { SliceType res; if (this.isEmpty) return res; res.privateMake(head_,head_.prev,length_); // res.tail_ = // res.head_ = head_; // res.length_ = length_; return res; } /** Get a ReadOnly view of the container */ .CircularList!(Value, true, Alloc) readonly() { .CircularList!(Value, true, Alloc) res; res = *cast(typeof(&res))this; return res; } /** Get a read-write view of the container */ .CircularList!(Value, false, Alloc) readwrite() { .CircularList!(Value, false, Alloc) res; res = *cast(typeof(&res))this; return res; } private Node* tail_() { if (this.isEmpty) return null; return head_.prev; } static if (!ReadOnly) { /** Rotate the list by n items. If n is negative the rotation is * reversed. */ void rotate(int n = 1) { if (n >= 0) { while (n-- > 0) head_ = head_.next; } else { while (++n < 0) head_ = head_.prev; } } /** Clear all contents. */ void clear() { static if (is(Alloc == GCAllocator)) { } else { Node* i = head_; if (i !is null) i.prev.next = null; while (i !is null) { Node* next = i.next; Alloc.gcFree(i); i = next; } } *this = CircularList.init; } /** Appends an item to the tail of the list. If the target list is * a sub-list call addAfter instead of addTail to insert an item * after a sub-list. */ void addTail(Value v) { Node* n = newNode(); n.data = v; addNode(n); } private void link(Node* a, Node* b) { a.next = b; b.prev = a; } /** Adds a node before head. */ private void addNode(Node* n) { if (this.isEmpty) { link(n,n); head_ = n; length_ = 1; } else { link(tail_,n); link(n,head_); if (length_) length_++; } } /** Appends a list to the tail of the target list. If the target * list is a sub-list call addAfter instead of addTail to insert * another list after a sub-list. */ void addTail(CircularList v) { addHead(v); } mixin MListCatOperators!(CircularList); // Helper function for take and remove private Node* takeTailHelper() { if (this.isEmpty) throw new IndexOutOfBoundsException(); Node* v = tail_; if (head_ && head_ is tail_) { head_ = null; } else { link(v.prev,v.next); } return v; } /** Removes and returns the tail item of the list. The node that * contained the item may be reused in future additions to the * list. To prevent the node from being reused call trim or * call remove with a sublist containing the last item. If * the target list is empty an IndexOutOfBoundsException is thrown * unless version=MinTLNoIndexChecking is set. */ Value takeTail() { Node* v = takeTailHelper(); Value data = v.data; freeNode(v); if (length_) length_--; return data; } /** Removes the tail item of the list. */ void removeTail() { Node* v = takeTailHelper(); if (length_) length_--; freeNode(v); } /** Prepends an item to the head of the target list. If the target * list is a sub-list call addBefore instead of addHead to insert an * item before a sub-list. */ void addHead(Value v) { Node* n = new Node; n.data = v; addNode(n); head_ = n; } /** Prepends a list to the head of the target list. If the target * list is a sub-list call addBefore instead of addHead to insert a * list before a sub-list. */ void addHead(CircularList v) { if (v.isEmpty) return; if (this.isEmpty) { *this = v; return; } Node* vt = v.tail_; link(tail_,v.head_); link(vt,head_); head_ = v.head_; length_ = increaseLength(length_, v.length_); } // Helper function for take and remove private Node* takeHeadHelper() { if (this.isEmpty) throw new IndexOutOfBoundsException(); Node* v = head_; if (head_ && head_ is tail_) { head_ = null; } else { link(v.prev,v.next); } return v; } /** Removes and returns the head item of the list. The node that * contained the item may be reused in future additions to the * list. To prevent the node from being reused call trim or * call remove with a sublist containing the last item. If * the target list is empty an IndexOutOfBoundsException is thrown * unless version=MinTLNoIndexChecking is set. */ Value takeHead() { Node* v = takeHeadHelper(); Value data = v.data; if (length_) length_--; freeNode(v); return data; } /** Removes the head item of the list. */ void removeHead() { Node* v = takeHeadHelper(); if (length_) length_--; freeNode(v); } /** Insert a list before a sub-list. */ void addBefore(SliceType subv, SliceType v) { if (v.isEmpty) return; if (subv.isEmpty) throw new IndexOutOfBoundsException(); Node* t = subv.head_; link(t.prev,v.head_); link(v.tail_,t); if (t is head_) head_ = v.head_; length_ = increaseLength(length_, v.length_); } /** Insert a list after a sub-list. */ void addAfter(SliceType subv, SliceType v) { if (v.isEmpty) return; if (subv.isEmpty) throw new IndexOutOfBoundsException(); Node* t = subv.tail_; link(v.tail_,t.next); link(t,v.head_); length_ = increaseLength(length_, v.length_); } /** Removes a sub-list from the list entirely. */ void remove(SliceType sublist) { if (sublist.isEmpty) return; Node* h = sublist.head_; Node* t = sublist.tail_; if (h is head_ && t is tail_) { head_ = null; length_ = 0; return; } if (h is head_) head_ = t.next; link(h.prev,t.next); h.prev = null; t.next = null; length_ = decreaseLength(length_, sublist.length_); } /** Removes an item if present. */ void remove(size_t index) { remove(opSlice(index, index+1)); } /** Removes an item from the list and return its value, if present. */ Value take(size_t index) { SliceType item = opSlice(index, index+1); remove(item); Value val = item[0]; item.clear(); return val; } } // !ReadOnly /** Duplicates a list. The operation is O(n) where n is length of * the list. */ CircularList dup() { .CircularList!(Value,false,Alloc) res; foreach(ValueType val; *this) { res ~= val; } static if (ReadOnly) { return res.readonly; } else { return res; } } /** Create a one-item slice of the head. */ SliceType head() { SliceType res; if (this.isEmpty) return res; res.head_ = res.tail_ = head_; res.length_ = 1; return res; } /** Create a one-item slice of the tail. */ SliceType tail() { SliceType res; if (this.isEmpty) return res; res.head_ = res.tail_ = head_.prev; res.length_ = 1; return res; } /** Move a sub-list towards the tail by n items. */ void next(int n = 1, int end = 0) { if (length_) length_ -= n*end; while (n-- > 0) { if (end <= 0) head_ = head_.next; } } /** Iterate backwards over the list (from tail to head). This * should be called as the iteration parameter in a * foreach statement */ CircularListReverseIter!(Value,ReadOnly,Alloc) backwards() { CircularListReverseIter!(Value,ReadOnly,Alloc) res; res.list = this; return res; } /** * Helper functions for opApply with/without keys and * forward/backward order */ mixin MOpApplyImpl!(ContainerType); alias opApplyNoKey opApply; alias opApplyWithKey opApply; alias opApplyIter opApply; private Node* newNode() { static if (is(Alloc == GCAllocator)) { return new Node; } else { Node* p = cast(Node*)Alloc.gcMalloc(Node.sizeof); *p = Node.init; return p; } } private void freeNode(Node* n) { static if (is(Alloc == GCAllocator)) { } else { Alloc.gcFree(n); } } CircularList getThis(){return *this;} mixin MListAlgo!(CircularList, getThis); mixin MCommonList!(tail_, CircularList ); } // helper functions for adjusting length cache private size_t increaseLength(size_t len, size_t x) { return x ? (len? len+x : 0) : 0; } private size_t decreaseLength(size_t len, size_t x) { return x ? (len? len-x : 0) : 0; } // helper structure for backwards() struct CircularListReverseIter(Value,bit ReadOnly,Alloc) { mixin MReverseImpl!(List!(Value,ReadOnly,Alloc), CircularList!(Value,ReadOnly,Alloc)); } //version = MinTLVerboseUnittest; //version = MinTLUnittest; version (MinTLUnittest) { private import std.random; unittest { version (MinTLVerboseUnittest) printf("starting mintl.list unittest\n"); List!(int) x; x ~= 3; x ~= 4; assert( x[0] == 3 ); assert( x[1] == 4 ); assert( x.length == 2 ); List!(int) x2 = List!(int).make(3,4); assert( x == x2 ); List!(int) catt; catt = List!(int).make(1,2,3) ~ List!(int).make(4,5,6); assert( catt == List!(int).make(1,2,3,4,5,6) ); List!(int,false,MallocNoRoots) xm; xm ~= 3; xm ~= 4; assert( xm[0] == 3 ); assert( xm[1] == 4 ); assert( xm.length == 2 ); xm.clear(); assert( xm.isEmpty ); List!(real) x2s; x2s.add(cast(real)3,cast(real)4); assert( x2s[0] == 3 ); assert( x2s[1] == 4 ); // test addHead List!(int) y; y.addHead(4); y.addHead(3); // test == assert( x == y ); List!(int) w = x.dup; w ~= 5; assert( x != w); // test remove/take assert( w.takeTail() == 5 ); size_t wlen = w.length; w.addTail(6); w.removeTail(); assert( w.length() == wlen ); assert( w == x ); w.trim(); w ~= 5; // test reverse lists List!(int) z = y.dup; z.reverse(); assert( z[0] == 4 ); assert( z[1] == 3 ); // test foreach iteration foreach(size_t n, inout int val; z) { val = n*10; } assert( z[0] == 0 ); assert( z[1] == 10 ); foreach(size_t n, int val; y.backwards()) { assert(x[n] == val); } int n = 0; foreach(List!(int) itr; y) { assert(itr[0] == y[n++]); } // test slicing List!(int) v = w[1..3]; assert( v.length == 2 ); assert( v[0] == 4 ); assert( v[1] == 5 ); // test readonly List!(int,ReadOnly) rv = v.readonly; assert( rv.length == 2 ); assert( rv[0] == 4 ); assert( rv[1] == 5 ); assert( rv.head == rv[0 .. 1] ); assert( rv.tail == rv[1 .. 2] ); // test algorithms assert( v.opIn(5) == v.tail ); assert( v.count(5) == 1 ); assert( v.find(delegate int(inout int v){return v == 5;}) == v.tail ); v[0 .. 1].swap(v[1..2]); assert( v[0] == 5 ); assert( v[1] == 4 ); v.fill(10); assert( v[0] == 10 ); assert( v[1] == 10 ); List!(int) vsub; vsub.add(4,5); v.copy(vsub); assert( v[0] == 4 ); assert( v[1] == 5 ); // test another node type List!(char[]) str; str.add("hello","world"); assert( str[str.length-1] == "world" ); // test sub-list spanning List!(int) tmp; int[10] tmp2; tmp2[3] = 100; tmp2[8] = 200; foreach(int x;tmp2) tmp ~= x; List!(int) a,b,c; a = tmp[3..5]; b = tmp[7..9]; c = tmp[a..b]; assert( c.length == 6 ); assert( c[0] == 100 ); assert( c[5] == 200 ); // CircularList testing CircularList!(int) cx; cx ~= 3; cx ~= 4; assert( cx[0] == 3 ); assert( cx[1] == 4 ); assert( cx.length == 2 ); CircularList!(int) cx2; cx2.add(3,4); assert( cx == cx2 ); // test addHead CircularList!(int) cy; cy.addHead(4); cy.addHead(3); // test == assert( cx == cy ); CircularList!(int) cw = cx.dup; cw ~= 5; assert( cx != cw); // test remove assert( cw.takeTail() == 5 ); wlen = cw.length; cw.addTail(6); cw.removeTail(); assert( cw.length() == wlen ); assert( cw == cx ); cw ~= 5; // test reverse lists CircularList!(int) cz = cy.dup; cz.reverse(); assert( cz[0] == 4 ); assert( cz[1] == 3 ); // test foreach iteration foreach(size_t n, inout int val; cz) { val = n*10; } assert( cz[0] == 0 ); assert( cz[1] == 10 ); foreach(size_t n, int val; cy.backwards()) { assert(cx[n] == val); } n = 0; foreach(List!(int) itr; cy) { assert(itr[0] == cy[n++]); } // test slicing List!(int) cv = w[1..3]; assert( cv.length == 2 ); assert( cv[0] == 4 ); assert( cv[1] == 5 ); // test algorithms assert( cv.opIn(5) == v.tail ); assert( cv.count(5) == 1 ); // test another node type CircularList!(char[]) cstr; cstr.add("hello","world"); assert( cstr[cstr.length-1] == "world" ); // test sub-list spanning CircularList!(int,false,MallocNoRoots) ctmp; tmp2[3] = 100; tmp2[8] = 200; foreach(int x;tmp2) ctmp ~= x; List!(int,false,MallocNoRoots) ca,cb,cc; ca = ctmp[3..5]; cb = ctmp[7..9]; cc = ctmp[ca..cb]; assert( cc.length == 6 ); assert( cc[0] == 100 ); assert( cc[5] == 200 ); ctmp.clear(); assert( ctmp.isEmpty ); // test simple sorting List!(int) s1,s12; s1.add(40,300,-20,100,400,200); s12 = s1.dup; s1.sort(); List!(int) s2 = List!(int).make(-20,40,100,200,300,400); //List!(int) s2 = s2.make(-20,40,100,200,300,400); assert( s1 == s2 ); // sort a slice in-place s12[1..4].sort(); s2.clear(); s2.add(40,-20,100,300,400,200); assert( s12 == s2 ); // test a large sort with default order List!(double) s3; for (int k=0;k<1000;k++) { s3 ~= 1.0*rand()/100000.0 - 500000.0; } List!(double) s4 = s3.dup; s3.sort(); for (int k=0;k<999;k++) { assert( s3[k] <= s3[k+1] ); } // test a large sort with custom order int cmp(double*x,double*y){return *x>*y?-1:*x==*y?0:1;} s4.sort(&cmp); for (int k=0;k<999;k++) { assert( s4[k] >= s4[k+1] ); } version (MinTLVerboseUnittest) printf("finished mintl.list unittest\n"); } }