//******************************
// Written by Peter Golde
// Copyright (c) 2004-2005, Wintellect
//
// Use and restribution of this code is subject to the license agreement
// contained in the file "License.txt" accompanying this file.
//******************************
using System;
using System.Collections.Generic;
using System.Collections;
// CONSIDER: always create a small initial buffer, so that the checks in Add for a null buffer aren't needed. This
// would improve performance slightly, but make empty Deque's take more memory.
namespace Wintellect.PowerCollections
{
///
/// The Deque class implements a type of list known as a Double Ended Queue. A Deque
/// is quite similar to a List, in that items have indices (starting at 0), and the item at any
/// index can be efficiently retrieved. The difference between a List and a Deque lies in the
/// efficiency of inserting elements at the beginning. In a List, items can be efficiently added
/// to the end, but inserting an item at the beginning of the List is slow, taking time
/// proportional to the size of the List. In a Deque, items can be added to the beginning
/// or end equally efficiently, regardless of the number of items in the Deque. As a trade-off
/// for this increased flexibility, Deque is somewhat slower than List (but still constant time) when
/// being indexed to get or retrieve elements.
///
///
/// The Deque class can also be used as a more flexible alternative to the Queue
/// and Stack classes. Deque is as efficient as Queue and Stack for adding or removing items,
/// but is more flexible: it allows access
/// to all items in the queue, and allows adding or removing from either end.
/// Deque is implemented as a ring buffer, which is grown as necessary. The size
/// of the buffer is doubled whenever the existing capacity is too small to hold all the
/// elements.
///
/// The type of items stored in the Deque.
[Serializable]
public class Deque : ListBase, ICloneable
{
// The initial size of the buffer.
private const int INITIAL_SIZE = 8;
// A ring buffer containing all the items in the deque. Shrinks or grows as needed.
// Except temporarily during an add, there is always at least one empty item.
private T[] buffer;
// Index of the first item (index 0) in the deque.
// Always in the range 0 through buffer.Length - 1.
private int start;
// Index just beyond the last item in the deque. If equal to start, the deque is empty.
// Always in the range 0 through buffer.Length - 1.
private int end;
// Holds the change stamp for the collection.
private int changeStamp;
///
/// Must be called whenever there is a structural change in the tree. Causes
/// changeStamp to be changed, which causes any in-progress enumerations
/// to throw exceptions.
///
private void StopEnumerations()
{
++changeStamp;
}
///
/// Checks the given stamp against the current change stamp. If different, the
/// collection has changed during enumeration and an InvalidOperationException
/// must be thrown
///
/// changeStamp at the start of the enumeration.
private void CheckEnumerationStamp(int startStamp)
{
if (startStamp != changeStamp) {
throw new InvalidOperationException(Strings.ChangeDuringEnumeration);
}
}
///
/// Create a new Deque that is initially empty.
///
public Deque()
{
}
///
/// Create a new Deque initialized with the items from the passed collection,
/// in order.
///
/// A collection of items to initialize the Deque with.
public Deque(IEnumerable collection)
{
AddManyToBack(collection);
}
///
/// Gets the number of items currently stored in the Deque. The last item
/// in the Deque has index Count-1.
///
/// Getting the count of items in the Deque takes a small constant
/// amount of time.
/// The number of items stored in this Deque.
public sealed override int Count
{
get {
if (end >= start)
return end - start;
else
return end + buffer.Length - start;
}
}
///
/// Copies all the items in the Deque into an array.
///
/// Array to copy to.
/// Starting index in to copy to.
public sealed override void CopyTo(T[] array, int arrayIndex)
{
if (array == null)
throw new ArgumentNullException("array");
// This override is provided to give a more efficient implementation to CopyTo than
// the default one provided by CollectionBase.
int length = (buffer == null) ? 0 : buffer.Length;
if (start > end) {
Array.Copy(buffer, start, array, arrayIndex, length - start);
Array.Copy(buffer, 0, array, arrayIndex + length - start, end);
}
else {
if (end > start)
Array.Copy(buffer, start, array, arrayIndex, end - start);
}
}
///
/// Gets or sets the capacity of the Deque. The Capacity is the number of
/// items that this Deque can hold without expanding its internal buffer. Since
/// Deque will automatically expand its buffer when necessary, in almost all cases
/// it is unnecessary to worry about the capacity. However, if it is known that a
/// Deque will contain exactly 1000 items eventually, it can slightly improve
/// efficiency to set the capacity to 1000 up front, so that the Deque does not
/// have to expand automatically.
///
/// The number of items that this Deque can hold without expanding its
/// internal buffer.
/// The capacity is being set
/// to less than Count, or to too large a value.
public int Capacity
{
get
{
if (buffer == null)
return 0;
else
return buffer.Length - 1;
}
set
{
if (value < Count)
throw new ArgumentOutOfRangeException("value", Strings.CapacityLessThanCount);
if (value > int.MaxValue - 1)
throw new ArgumentOutOfRangeException("value");
if (value == Capacity)
return;
T[] newBuffer = new T[value + 1];
CopyTo(newBuffer, 0);
end = Count;
start = 0;
buffer = newBuffer;
}
}
///
/// Trims the amount of memory used by the Deque by changing
/// the Capacity to be equal to Count. If no more items will be added
/// to the Deque, calling TrimToSize will reduce the amount of memory
/// used by the Deque.
///
public void TrimToSize()
{
Capacity = Count;
}
///
/// Removes all items from the Deque.
///
/// Clearing the Deque takes a small constant amount of time, regardless of
/// how many items are currently in the Deque.
public sealed override void Clear()
{
StopEnumerations();
buffer = null;
start = end = 0;
}
///
/// Gets or sets an item at a particular index in the Deque.
///
/// Getting or setting the item at a particular index takes a small constant amount
/// of time, no matter what index is used.
/// The index of the item to retrieve or change. The front item has index 0, and
/// the back item has index Count-1.
/// The value at the indicated index.
/// The index is less than zero or greater than or equal
/// to Count.
public sealed override T this[int index]
{
get
{
int i = index + start;
if (i < start) // handles both the case where index < 0, or the above addition overflow to a negative number.
throw new ArgumentOutOfRangeException("index");
if (end >= start) {
if (i >= end)
throw new ArgumentOutOfRangeException("index");
return buffer[i];
}
else {
int length = buffer.Length;
if (i >= length) {
i -= length;
if (i >= end)
throw new ArgumentOutOfRangeException("index");
}
return buffer[i];
}
}
set
{
// Like List, we stop enumerations after a set operation. There is no
// technical reason to do this, however.
StopEnumerations();
int i = index + start;
if (i < start) // handles both the case where index < 0, or the above addition overflow to a negative number.
throw new ArgumentOutOfRangeException("index");
if (end >= start) {
if (i >= end)
throw new ArgumentOutOfRangeException("index");
buffer[i] = value;
}
else {
int length = buffer.Length;
if (i >= length) {
i -= length;
if (i >= end)
throw new ArgumentOutOfRangeException("index");
}
buffer[i] = value;
}
}
}
///
/// Enumerates all of the items in the list, in order. The item at index 0
/// is enumerated first, then the item at index 1, and so on. If the items
/// are added to or removed from the Deque during enumeration, the
/// enumeration ends with an InvalidOperationException.
///
/// An IEnumerator<T> that enumerates all the
/// items in the list.
/// The Deque has an item added or deleted during the enumeration.
public sealed override IEnumerator GetEnumerator()
{
int startStamp = changeStamp;
int count = Count;
for (int i = 0; i < count; ++i) {
yield return this[i];
CheckEnumerationStamp(startStamp);
}
}
///
/// Creates the initial buffer and initialized the Deque to contain one initial
/// item.
///
/// First and only item for the Deque.
private void CreateInitialBuffer(T firstItem)
{
// The buffer hasn't been created yet.
buffer = new T[INITIAL_SIZE];
start = 0;
end = 1;
buffer[0] = firstItem;
return;
}
///
/// Inserts a new item at the given index in the Deque. All items at indexes
/// equal to or greater than move up one index
/// in the Deque.
///
/// The amount of time to insert an item in the Deque is proportional
/// to the distance of index from the closest end of the Deque:
/// O(Min(, Count - )).
/// Thus, inserting an item at the front or end of the Deque is always fast; the middle of
/// of the Deque is the slowest place to insert.
///
/// The index in the Deque to insert the item at. After the
/// insertion, the inserted item is located at this index. The
/// front item in the Deque has index 0.
/// The item to insert at the given index.
/// is
/// less than zero or greater than Count.
public sealed override void Insert(int index, T item)
{
StopEnumerations();
int count = Count;
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException("index");
if (buffer == null) {
// The buffer hasn't been created yet.
CreateInitialBuffer(item);
return;
}
int length = buffer.Length;
int i; // The location the new item was placed at.
if (index < count / 2) {
// Inserting into the first half of the list. Move items with
// lower index down in the buffer.
start -= 1;
if (start < 0)
start += length;
i = index + start;
if (i >= length) {
i -= length;
if (length - 1 > start)
Array.Copy(buffer, start + 1, buffer, start, length - 1 - start);
buffer[length - 1] = buffer[0]; // unneeded if end == 0, but doesn't hurt
if (i > 0)
Array.Copy(buffer, 1, buffer, 0, i);
}
else {
if (i > start)
Array.Copy(buffer, start + 1, buffer, start, i - start);
}
}
else {
// Inserting into the last half of the list. Move items with higher
// index up in the buffer.
i = index + start;
if (i >= length)
i -= length;
if (i <= end) {
if (end > i)
Array.Copy(buffer, i, buffer, i + 1, end - i);
end += 1;
if (end >= length)
end -= length;
}
else {
if (end > 0)
Array.Copy(buffer, 0, buffer, 1, end);
buffer[0] = buffer[length - 1];
if (length - 1 > i)
Array.Copy(buffer, i, buffer, i + 1, length - 1 - i);
end += 1;
}
}
buffer[i] = item;
if (start == end)
IncreaseBuffer();
}
///
/// Inserts a collection of items at the given index in the Deque. All items at indexes
/// equal to or greater than increase their indices in the Deque
/// by the number of items inserted.
///
/// The amount of time to insert a collection in the Deque is proportional
/// to the distance of index from the closest end of the Deque, plus the number of items
/// inserted (M):
/// O(M + Min(, Count - )).
///
/// The index in the Deque to insert the collection at. After the
/// insertion, the first item of the inserted collection is located at this index. The
/// front item in the Deque has index 0.
/// The collection of items to insert at the given index.
/// is
/// less than zero or greater than Count.
public void InsertRange(int index, IEnumerable collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
StopEnumerations();
int count = Count;
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException("index");
// We need an ICollection, because we need the count of the collection.
// If needed, copy the items to a temporary list.
ICollection coll;
if (collection is ICollection)
coll = (ICollection) collection;
else {
coll = new List(collection);
}
if (coll.Count == 0)
return; // nothing to do.
// Create enough capacity in the list for the new items.
if (Capacity < Count + coll.Count)
Capacity = Count + coll.Count;
int length = buffer.Length;
int s, d;
if (index < count / 2) {
// Inserting into the first half of the list. Move items with
// lower index down in the buffer.
s = start;
d = s - coll.Count;
if (d < 0)
d += length;
start = d;
int c = index;
while (c > 0) {
int chunk = c;
if (length - d < chunk)
chunk = length - d;
if (length - s < chunk)
chunk = length - s;
Array.Copy(buffer, s, buffer, d, chunk);
c -= chunk;
if ((d += chunk) >= length)
d -= length;
if ((s += chunk) >= length)
s -= length;
}
}
else {
// Inserting into the last half of the list. Move items with higher
// index up in the buffer.
s = end;
d = s + coll.Count;
if (d >= length)
d -= length;
end = d;
int move = count - index; // number of items at end to move
int c = move;
while (c > 0) {
int chunk = c;
if (d > 0 && d < chunk)
chunk = d;
if (s > 0 && s < chunk)
chunk = s;
if ((d -= chunk) < 0)
d += length;
if ((s -= chunk) < 0)
s += length;
Array.Copy(buffer, s, buffer, d, chunk);
c -= chunk;
}
d -= coll.Count;
if (d < 0)
d += length;
}
// Copy the items into the space vacated, which starts at d.
foreach (T item in coll) {
buffer[d] = item;
if (++d >= length)
d -= length;
}
}
///
/// Removes the item at the given index in the Deque. All items at indexes
/// greater than move down one index
/// in the Deque.
///
/// The amount of time to delete an item in the Deque is proportional
/// to the distance of index from the closest end of the Deque:
/// O(Min(, Count - 1 - )).
/// Thus, deleting an item at the front or end of the Deque is always fast; the middle of
/// of the Deque is the slowest place to delete.
///
/// The index in the list to remove the item at. The
/// first item in the list has index 0.
/// is
/// less than zero or greater than or equal to Count.
public sealed override void RemoveAt(int index)
{
StopEnumerations();
int count = Count;
if (index < 0 || index >= count)
throw new ArgumentOutOfRangeException("index");
int length = buffer.Length;
int i; // index of removed item
if (index < count / 2) {
// Removing in the first half of the list. Move items with
// lower index up in the buffer.
i = index + start;
if (i >= length) {
i -= length;
if (i > 0)
Array.Copy(buffer, 0, buffer, 1, i);
buffer[0] = buffer[length - 1];
if (length - 1 > start)
Array.Copy(buffer, start, buffer, start + 1, length - 1 - start);
}
else {
if (i > start)
Array.Copy(buffer, start, buffer, start + 1, i - start);
}
buffer[start] = default(T);
start += 1;
if (start >= length)
start -= length;
}
else {
// Removing in the second half of the list. Move items with
// higher indexes down in the buffer.
i = index + start;
if (i >= length)
i -= length;
end -= 1;
if (end < 0)
end = length - 1;
if (i <= end) {
if (end > i)
Array.Copy(buffer, i + 1, buffer, i, end - i);
}
else {
if (length - 1 > i)
Array.Copy(buffer, i + 1, buffer, i, length - 1 - i);
buffer[length - 1] = buffer[0];
if (end > 0)
Array.Copy(buffer, 1, buffer, 0, end);
}
buffer[end] = default(T);
}
}
///
/// Removes a range of items at the given index in the Deque. All items at indexes
/// greater than move down indices
/// in the Deque.
///
/// The amount of time to delete items in the Deque is proportional
/// to the distance to the closest end of the Deque:
/// O(Min(, Count - - )).
///
/// The index in the list to remove the range at. The
/// first item in the list has index 0.
/// The number of items to remove.
/// is
/// less than zero or greater than or equal to Count, or is less than zero
/// or too large.
public void RemoveRange(int index, int count)
{
StopEnumerations();
int dequeCount = Count;
if (index < 0 || index >= dequeCount)
throw new ArgumentOutOfRangeException("index");
if (count < 0 || count > dequeCount - index)
throw new ArgumentOutOfRangeException("count");
if (count == 0)
return;
int length = buffer.Length;
int s, d;
if (index < dequeCount / 2) {
// Removing in the first half of the list. Move items with
// lower index up in the buffer.
s = start + index;
if (s >= length)
s -= length;
d = s + count;
if (d >= length)
d -= length;
int c = index;
while (c > 0) {
int chunk = c;
if (d > 0 && d < chunk)
chunk = d;
if (s > 0 && s < chunk)
chunk = s;
if ((d -= chunk) < 0)
d += length;
if ((s -= chunk) < 0)
s += length;
Array.Copy(buffer, s, buffer, d, chunk);
c -= chunk;
}
// At this point, s == start
for (c = 0; c < count; ++c) {
buffer[s] = default(T);
if (++s >= length)
s -= length;
}
start = s;
}
else {
// Removing in the second half of the list. Move items with
// higher indexes down in the buffer.
int move = dequeCount - index - count;
s = end - move;
if (s < 0)
s += length;
d = s - count;
if (d < 0)
d += length;
int c = move;
while (c > 0) {
int chunk = c;
if (length - d < chunk)
chunk = length - d;
if (length - s < chunk)
chunk = length - s;
Array.Copy(buffer, s, buffer, d, chunk);
c -= chunk;
if ((d += chunk) >= length)
d -= length;
if ((s += chunk) >= length)
s -= length;
}
// At this point, s == end.
for (c = 0; c < count; ++c) {
if (--s < 0)
s += length;
buffer[s] = default(T);
}
end = s;
}
}
///
/// Increase the amount of buffer space. When calling this method, the Deque
/// must not be empty. If start and end are equal, that indicates a completely
/// full Deque.
///
private void IncreaseBuffer()
{
int count = Count;
int length = buffer.Length;
T[] newBuffer = new T[length * 2];
if (start >= end) {
Array.Copy(buffer, start, newBuffer, 0, length - start);
Array.Copy(buffer, 0, newBuffer, length - start, end);
end = end + length - start;
start = 0;
}
else {
Array.Copy(buffer, start, newBuffer, 0, end - start);
end = end - start;
start = 0;
}
buffer = newBuffer;
}
///
/// Adds an item to the front of the Deque. The indices of all existing items
/// in the Deque are increased by 1. This method is
/// equivalent to Insert(0, item) but is a little more
/// efficient.
///
/// Adding an item to the front of the Deque takes
/// a small constant amount of time, regardless of how many items are in the Deque.
/// The item to add.
public void AddToFront(T item)
{
StopEnumerations();
if (buffer == null) {
// The buffer hasn't been created yet.
CreateInitialBuffer(item);
return;
}
if (--start < 0)
start += buffer.Length;
buffer[start] = item;
if (start == end)
IncreaseBuffer();
}
///
/// Adds a collection of items to the front of the Deque. The indices of all existing items
/// in the Deque are increased by the number of items inserted. The first item in the added collection becomes the
/// first item in the Deque.
///
/// This method takes time O(M), where M is the number of items in the
/// .
/// The collection of items to add.
public void AddManyToFront(IEnumerable collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
InsertRange(0, collection);
}
///
/// Adds an item to the back of the Deque. The indices of all existing items
/// in the Deque are unchanged. This method is
/// equivalent to Insert(Count, item) but is a little more
/// efficient.
///
/// Adding an item to the back of the Deque takes
/// a small constant amount of time, regardless of how many items are in the Deque.
/// The item to add.
public void AddToBack(T item)
{
StopEnumerations();
if (buffer == null) {
// The buffer hasn't been created yet.
CreateInitialBuffer(item);
return;
}
buffer[end] = item;
if (++end >= buffer.Length)
end -= buffer.Length;
if (start == end)
IncreaseBuffer();
}
///
/// Adds an item to the back of the Deque. The indices of all existing items
/// in the Deque are unchanged. This method is
/// equivalent to AddToBack(item).
///
/// Adding an item to the back of the Deque takes
/// a small constant amount of time, regardless of how many items are in the Deque.
/// The item to add.
public sealed override void Add(T item)
{
AddToBack(item);
}
///
/// Adds a collection of items to the back of the Deque. The indices of all existing items
/// in the Deque are unchanged. The last item in the added collection becomes the
/// last item in the Deque.
///
/// This method takes time O(M), where M is the number of items in the
/// .
/// The collection of item to add.
public void AddManyToBack(IEnumerable collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
foreach (T item in collection)
AddToBack(item);
}
///
/// Removes an item from the front of the Deque. The indices of all existing items
/// in the Deque are decreased by 1. This method is
/// equivalent to RemoveAt(0) but is a little more
/// efficient.
///
/// Removing an item from the front of the Deque takes
/// a small constant amount of time, regardless of how many items are in the Deque.
/// The item that was removed.
/// The Deque is empty.
public T RemoveFromFront()
{
if (start == end)
throw new InvalidOperationException(Strings.CollectionIsEmpty);
StopEnumerations();
T item = buffer[start];
buffer[start] = default(T);
if (++start >= buffer.Length)
start -= buffer.Length;
return item;
}
///
/// Removes an item from the back of the Deque. The indices of all existing items
/// in the Deque are unchanged. This method is
/// equivalent to RemoveAt(Count-1) but is a little more
/// efficient.
///
/// Removing an item from the back of the Deque takes
/// a small constant amount of time, regardless of how many items are in the Deque.
/// The Deque is empty.
public T RemoveFromBack()
{
if (start == end)
throw new InvalidOperationException(Strings.CollectionIsEmpty);
StopEnumerations();
if (--end < 0)
end += buffer.Length;
T item = buffer[end];
buffer[end] = default(T);
return item;
}
///
/// Retreives the item currently at the front of the Deque. The Deque is
/// unchanged. This method is
/// equivalent to deque[0] (except that a different exception is thrown).
///
/// Retreiving the item at the front of the Deque takes
/// a small constant amount of time, regardless of how many items are in the Deque.
/// The item at the front of the Deque.
/// The Deque is empty.
public T GetAtFront()
{
if (start == end)
throw new InvalidOperationException(Strings.CollectionIsEmpty);
return buffer[start];
}
///
/// Retreives the item currently at the back of the Deque. The Deque is
/// unchanged. This method is
/// equivalent to deque[deque.Count - 1] (except that a different exception is thrown).
///
/// Retreiving the item at the back of the Deque takes
/// a small constant amount of time, regardless of how many items are in the Deque.
/// The item at the back of the Deque.
/// The Deque is empty.
public T GetAtBack()
{
if (start == end)
throw new InvalidOperationException(Strings.CollectionIsEmpty);
if (end == 0)
return buffer[buffer.Length - 1];
else
return buffer[end - 1];
}
///
/// Creates a new Deque that is a copy of this one.
///
/// Copying a Deque takes O(N) time, where N is the number of items in this Deque..
/// A copy of the current deque.
public Deque Clone()
{
return new Deque(this);
}
///
/// Creates a new Deque that is a copy of this one.
///
/// Copying a Deque takes O(N) time, where N is the number of items in this Deque..
/// A copy of the current deque.
object ICloneable.Clone()
{
return this.Clone();
}
///
/// Makes a deep clone of this Deque. A new Deque is created with a clone of
/// each element of this set, by calling ICloneable.Clone on each element. If T is
/// a value type, then each element is copied as if by simple assignment.
///
/// If T is a reference type, it must implement
/// ICloneable. Otherwise, an InvalidOperationException is thrown.
/// Cloning the Deque takes time O(N), where N is the number of items in the Deque.
/// The cloned Deque.
/// T is a reference type that does not implement ICloneable.
public Deque CloneContents()
{
bool itemIsValueType;
if (!Util.IsCloneableType(typeof(T), out itemIsValueType))
throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, typeof(T).FullName));
Deque clone = new Deque();
// Clone each item, and add it to the new ordered set.
foreach (T item in this) {
T itemClone;
if (itemIsValueType)
itemClone = item;
else {
if (item == null)
itemClone = default(T); // Really null, because we know T is a reference type
else
itemClone = (T)(((ICloneable)item).Clone());
}
clone.AddToBack(itemClone);
}
return clone;
}
#if DEBUG
///
/// Print out the internal state of the Deque for debugging.
///
internal void Print()
{
Console.WriteLine("length={0} start={1} end={2}", buffer.Length, start, end);
for (int i = 0; i < buffer.Length; ++i) {
if (i == start)
Console.Write("start-> ");
else
Console.Write(" ");
if (i == end)
Console.Write("end-> ");
else
Console.Write(" ");
Console.WriteLine("{0,4} {1}", i, buffer[i]);
}
Console.WriteLine();
}
#endif // DEBUG
}
}