Deque (Double-Ended Queue)
namespace DataStructures.Deque;
/// <summary>
/// Implementation of a Deque (Double-Ended Queue) data structure.
/// A deque allows insertion and deletion of elements from both ends (front and rear).
/// This implementation uses a circular array for efficient operations.
///
/// Key Features:
/// - O(1) time complexity for AddFront, AddRear, RemoveFront, RemoveRear operations
/// - O(1) amortized time for insertions (due to dynamic resizing)
/// - Space efficient with circular array implementation
/// - Automatic capacity doubling when full
///
/// Use Cases:
/// - Implementing sliding window algorithms
/// - Palindrome checking
/// - Undo/Redo functionality
/// - Task scheduling with priority at both ends
///
/// Reference: "Data Structures and Algorithms in C#" by Michael T. Goodrich.
/// </summary>
/// <typeparam name="T">The type of elements in the deque.</typeparam>
public class Deque<T>
{
// Internal circular array to store elements
private T[] items;
// Index of the front element (next element to remove from front)
private int front;
// Index where the next element will be added at rear
private int rear;
// Current number of elements in the deque
private int count;
/// <summary>
/// Initializes a new instance of the <see cref="Deque{T}" /> class with default capacity.
/// Default capacity is 16 elements, which provides a good balance between
/// memory usage and avoiding early resizing for typical use cases.
/// </summary>
public Deque()
: this(16)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Deque{T}" /> class with specified capacity.
/// </summary>
/// <param name="capacity">The initial capacity of the deque.</param>
/// <exception cref="ArgumentException">Thrown when capacity is less than 1.</exception>
public Deque(int capacity)
{
if (capacity < 1)
{
throw new ArgumentException("Capacity must be at least 1.", nameof(capacity));
}
items = new T[capacity];
front = 0;
rear = 0;
count = 0;
}
/// <summary>
/// Gets the number of elements in the deque.
/// </summary>
public int Count => count;
/// <summary>
/// Gets a value indicating whether the deque is empty.
/// </summary>
public bool IsEmpty => count == 0;
/// <summary>
/// Adds an element to the front of the deque.
/// This operation is O(1) time complexity (amortized due to occasional resizing).
/// </summary>
/// <param name="item">The item to add.</param>
/// <example>
/// deque.AddFront(5); // Deque: [5].
/// deque.AddFront(3); // Deque: [3, 5].
/// </example>
public void AddFront(T item)
{
// Check if we need to resize before adding
if (count == items.Length)
{
Resize();
}
// Move front pointer backward in circular fashion
// Adding items.Length ensures the result is always positive
front = (front - 1 + items.Length) % items.Length;
items[front] = item;
count++;
}
/// <summary>
/// Adds an element to the rear of the deque.
/// This operation is O(1) time complexity (amortized due to occasional resizing).
/// </summary>
/// <param name="item">The item to add.</param>
/// <example>
/// deque.AddRear(5); // Deque: [5].
/// deque.AddRear(7); // Deque: [5, 7].
/// </example>
public void AddRear(T item)
{
// Check if we need to resize before adding
if (count == items.Length)
{
Resize();
}
// Add item at rear position
items[rear] = item;
// Move rear pointer forward in circular fashion
rear = (rear + 1) % items.Length;
count++;
}
/// <summary>
/// Removes and returns the element at the front of the deque.
/// This operation is O(1) time complexity.
/// </summary>
/// <returns>The element at the front of the deque.</returns>
/// <exception cref="InvalidOperationException">Thrown when the deque is empty.</exception>
/// <example>
/// // Deque: [3, 5, 7].
/// int value = deque.RemoveFront(); // Returns 3, Deque: [5, 7].
/// </example>
public T RemoveFront()
{
// Validate that deque is not empty
if (IsEmpty)
{
throw new InvalidOperationException("Deque is empty.");
}
// Retrieve the front element
T item = items[front];
// Clear the reference to help garbage collection
items[front] = default!;
// Move front pointer forward in circular fashion
front = (front + 1) % items.Length;
count--;
return item;
}
/// <summary>
/// Removes and returns the element at the rear of the deque.
/// This operation is O(1) time complexity.
/// </summary>
/// <returns>The element at the rear of the deque.</returns>
/// <exception cref="InvalidOperationException">Thrown when the deque is empty.</exception>
/// <example>
/// // Deque: [3, 5, 7].
/// int value = deque.RemoveRear(); // Returns 7, Deque: [3, 5].
/// </example>
public T RemoveRear()
{
// Validate that deque is not empty
if (IsEmpty)
{
throw new InvalidOperationException("Deque is empty.");
}
// Move rear pointer backward to the last element
rear = (rear - 1 + items.Length) % items.Length;
// Retrieve the rear element
T item = items[rear];
// Clear the reference to help garbage collection
items[rear] = default!;
count--;
return item;
}
/// <summary>
/// Returns the element at the front of the deque without removing it.
/// This operation is O(1) time complexity and does not modify the deque.
/// </summary>
/// <returns>The element at the front of the deque.</returns>
/// <exception cref="InvalidOperationException">Thrown when the deque is empty.</exception>
/// <example>
/// // Deque: [3, 5, 7].
/// int value = deque.PeekFront(); // Returns 3, Deque unchanged: [3, 5, 7].
/// </example>
public T PeekFront()
{
// Validate that deque is not empty
if (IsEmpty)
{
throw new InvalidOperationException("Deque is empty.");
}
return items[front];
}
/// <summary>
/// Returns the element at the rear of the deque without removing it.
/// This operation is O(1) time complexity and does not modify the deque.
/// </summary>
/// <returns>The element at the rear of the deque.</returns>
/// <exception cref="InvalidOperationException">Thrown when the deque is empty.</exception>
/// <example>
/// // Deque: [3, 5, 7].
/// int value = deque.PeekRear(); // Returns 7, Deque unchanged: [3, 5, 7].
/// </example>
public T PeekRear()
{
// Validate that deque is not empty
if (IsEmpty)
{
throw new InvalidOperationException("Deque is empty.");
}
// Calculate the index of the last element (rear - 1 in circular array)
int rearIndex = (rear - 1 + items.Length) % items.Length;
return items[rearIndex];
}
/// <summary>
/// Removes all elements from the deque.
/// This operation is O(n) where n is the capacity of the internal array.
/// After clearing, the deque can be reused without reallocation.
/// </summary>
public void Clear()
{
// Clear all references in the array to help garbage collection
Array.Clear(items, 0, items.Length);
// Reset pointers to initial state
front = 0;
rear = 0;
count = 0;
}
/// <summary>
/// Converts the deque to an array.
/// This operation is O(n) where n is the number of elements.
/// The resulting array maintains the order from front to rear.
/// </summary>
/// <returns>An array containing all elements in the deque from front to rear.</returns>
/// <example>
/// // Deque: [3, 5, 7].
/// int[] array = deque.ToArray(); // Returns [3, 5, 7].
/// </example>
public T[] ToArray()
{
// Create result array with exact size needed
T[] result = new T[count];
int index = front;
// Copy elements from front to rear, handling circular wrap-around
for (int i = 0; i < count; i++)
{
result[i] = items[index];
index = (index + 1) % items.Length;
}
return result;
}
/// <summary>
/// Determines whether the deque contains a specific element.
/// This operation is O(n) where n is the number of elements.
/// Uses the default equality comparer for type T.
/// </summary>
/// <param name="item">The item to locate in the deque.</param>
/// <returns>true if the item is found; otherwise, false.</returns>
/// <example>
/// // Deque: [3, 5, 7].
/// bool exists = deque.Contains(5); // Returns true.
/// bool missing = deque.Contains(9); // Returns false.
/// </example>
public bool Contains(T item)
{
int index = front;
// Iterate through all elements in order from front to rear
for (int i = 0; i < count; i++)
{
// Use default equality comparer to compare elements
if (EqualityComparer<T>.Default.Equals(items[index], item))
{
return true;
}
// Move to next element in circular array
index = (index + 1) % items.Length;
}
return false;
}
/// <summary>
/// Resizes the internal array to accommodate more elements.
/// This is a private helper method called automatically when capacity is reached.
/// Doubles the capacity and reorganizes elements to start at index 0.
/// Time complexity: O(n) where n is the current number of elements.
/// </summary>
private void Resize()
{
// Double the capacity to reduce frequency of future resizes
int newCapacity = items.Length * 2;
T[] newItems = new T[newCapacity];
// Copy all elements to new array starting from index 0
// This "unwraps" the circular structure for simplicity
int index = front;
for (int i = 0; i < count; i++)
{
newItems[i] = items[index];
index = (index + 1) % items.Length;
}
// Replace old array with new larger array
items = newItems;
// Reset pointers: front at 0, rear at position after last element
front = 0;
rear = count;
}
}