From 03e740885b5db5a0b497da46079e5b1aef85fce9 Mon Sep 17 00:00:00 2001 From: codomposer Date: Tue, 4 Nov 2025 10:41:55 -0500 Subject: [PATCH 1/3] feat: add Kadane's Algorithm and Deque data structure with comprehensive tests and documentation --- .../Other/KadanesAlgorithmTests.cs | 216 ++++++++++ Algorithms/Other/KadanesAlgorithm.cs | 145 +++++++ DataStructures.Tests/Deque/DequeTests.cs | 383 ++++++++++++++++++ DataStructures/Deque/Deque.cs | 324 +++++++++++++++ 4 files changed, 1068 insertions(+) create mode 100644 Algorithms.Tests/Other/KadanesAlgorithmTests.cs create mode 100644 Algorithms/Other/KadanesAlgorithm.cs create mode 100644 DataStructures.Tests/Deque/DequeTests.cs create mode 100644 DataStructures/Deque/Deque.cs diff --git a/Algorithms.Tests/Other/KadanesAlgorithmTests.cs b/Algorithms.Tests/Other/KadanesAlgorithmTests.cs new file mode 100644 index 00000000..9a3170d0 --- /dev/null +++ b/Algorithms.Tests/Other/KadanesAlgorithmTests.cs @@ -0,0 +1,216 @@ +using Algorithms.Other; +using NUnit.Framework; +using System; + +namespace Algorithms.Tests.Other; + +/// +/// Comprehensive test suite for Kadane's Algorithm implementation. +/// Tests cover various scenarios including: +/// - Arrays with all positive numbers +/// - Arrays with mixed positive and negative numbers +/// - Arrays with all negative numbers +/// - Edge cases (single element, empty array, null array) +/// - Index tracking functionality +/// - Long integer support for large numbers +/// +public static class KadanesAlgorithmTests +{ + [Test] + public static void FindMaximumSubarraySum_WithPositiveNumbers_ReturnsCorrectSum() + { + // Arrange: When all numbers are positive, the entire array is the maximum subarray + int[] array = { 1, 2, 3, 4, 5 }; + + // Act + int result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: Sum of all elements = 1 + 2 + 3 + 4 + 5 = 15 + Assert.That(result, Is.EqualTo(15)); + } + + [Test] + public static void FindMaximumSubarraySum_WithMixedNumbers_ReturnsCorrectSum() + { + // Arrange: Classic example with mixed positive and negative numbers + // The maximum subarray is [4, -1, 2, 1] starting at index 3 + int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; + + // Act + int result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: Maximum sum is 4 + (-1) + 2 + 1 = 6 + Assert.That(result, Is.EqualTo(6)); // Subarray [4, -1, 2, 1] + } + + [Test] + public static void FindMaximumSubarraySum_WithAllNegativeNumbers_ReturnsLargestNegative() + { + // Arrange: When all numbers are negative, the algorithm returns the least negative number + // This represents a subarray of length 1 containing the largest (least negative) element + int[] array = { -5, -2, -8, -1, -4 }; + + // Act + int result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: -1 is the largest (least negative) number in the array + Assert.That(result, Is.EqualTo(-1)); + } + + [Test] + public static void FindMaximumSubarraySum_WithSingleElement_ReturnsThatElement() + { + // Arrange: Edge case with only one element + // The only possible subarray is the element itself + int[] array = { 42 }; + + // Act + int result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: The single element is both the subarray and its sum + Assert.That(result, Is.EqualTo(42)); + } + + [Test] + public static void FindMaximumSubarraySum_WithNullArray_ThrowsArgumentException() + { + // Arrange: Test defensive programming - null input validation + int[]? array = null; + + // Act & Assert: Should throw ArgumentException for null input + Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarraySum(array!)); + } + + [Test] + public static void FindMaximumSubarraySum_WithEmptyArray_ThrowsArgumentException() + { + // Arrange + int[] array = Array.Empty(); + + // Act & Assert + Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarraySum(array)); + } + + [Test] + public static void FindMaximumSubarraySum_WithAlternatingNumbers_ReturnsCorrectSum() + { + // Arrange: Alternating positive and negative numbers + // Despite negative values, the entire array gives the maximum sum + int[] array = { 5, -3, 5, -3, 5 }; + + // Act + int result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: Sum of entire array = 5 - 3 + 5 - 3 + 5 = 9 + Assert.That(result, Is.EqualTo(9)); // Entire array + } + + [Test] + public static void FindMaximumSubarrayWithIndices_ReturnsCorrectIndices() + { + // Arrange: Test the variant that returns indices of the maximum subarray + // Array: [-2, 1, -3, 4, -1, 2, 1, -5, 4] + // Index: 0 1 2 3 4 5 6 7 8 + int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; + + // Act + var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array); + + // Assert: Maximum subarray is [4, -1, 2, 1] from index 3 to 6 + Assert.That(maxSum, Is.EqualTo(6)); + Assert.That(startIndex, Is.EqualTo(3)); + Assert.That(endIndex, Is.EqualTo(6)); + } + + [Test] + public static void FindMaximumSubarrayWithIndices_WithSingleElement_ReturnsZeroIndices() + { + // Arrange + int[] array = { 10 }; + + // Act + var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array); + + // Assert + Assert.That(maxSum, Is.EqualTo(10)); + Assert.That(startIndex, Is.EqualTo(0)); + Assert.That(endIndex, Is.EqualTo(0)); + } + + [Test] + public static void FindMaximumSubarrayWithIndices_WithNullArray_ThrowsArgumentException() + { + // Arrange + int[]? array = null; + + // Act & Assert + Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarrayWithIndices(array!)); + } + + [Test] + public static void FindMaximumSubarraySum_WithLongArray_ReturnsCorrectSum() + { + // Arrange: Test the long integer overload with same values as int test + // Verifies that the algorithm works correctly with long data type + long[] array = { -2L, 1L, -3L, 4L, -1L, 2L, 1L, -5L, 4L }; + + // Act + long result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: Should produce same result as int version + Assert.That(result, Is.EqualTo(6L)); + } + + [Test] + public static void FindMaximumSubarraySum_WithLargeLongNumbers_ReturnsCorrectSum() + { + // Arrange: Test with large numbers that would overflow int type + // This demonstrates why the long overload is necessary + // Sum would be 1,500,000,000 which fits in long but is near int.MaxValue + long[] array = { 1000000000L, -500000000L, 1000000000L }; + + // Act + long result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: Entire array sum = 1,000,000,000 - 500,000,000 + 1,000,000,000 = 1,500,000,000 + Assert.That(result, Is.EqualTo(1500000000L)); + } + + [Test] + public static void FindMaximumSubarraySum_WithLongNullArray_ThrowsArgumentException() + { + // Arrange + long[]? array = null; + + // Act & Assert + Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarraySum(array!)); + } + + [Test] + public static void FindMaximumSubarraySum_WithZeros_ReturnsZero() + { + // Arrange: Edge case with all zeros + // Any subarray will have sum of 0 + int[] array = { 0, 0, 0, 0 }; + + // Act + int result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: Maximum sum is 0 + Assert.That(result, Is.EqualTo(0)); + } + + [Test] + public static void FindMaximumSubarraySum_WithMixedZerosAndNegatives_ReturnsZero() + { + // Arrange: Mix of zeros and negative numbers + // The best subarray is any single zero (or multiple zeros) + int[] array = { -5, 0, -3, 0, -2 }; + + // Act + int result = KadanesAlgorithm.FindMaximumSubarraySum(array); + + // Assert: Zero is better than any negative number + Assert.That(result, Is.EqualTo(0)); + } +} diff --git a/Algorithms/Other/KadanesAlgorithm.cs b/Algorithms/Other/KadanesAlgorithm.cs new file mode 100644 index 00000000..4050996e --- /dev/null +++ b/Algorithms/Other/KadanesAlgorithm.cs @@ -0,0 +1,145 @@ +namespace Algorithms.Other; + +/// +/// Kadane's Algorithm is used to find the maximum sum of a contiguous subarray +/// within a one-dimensional array of numbers. It has a time complexity of O(n). +/// This algorithm is a classic example of dynamic programming. +/// Reference: "Introduction to Algorithms" by Cormen, Leiserson, Rivest, and Stein (CLRS). +/// +public static class KadanesAlgorithm +{ + /// + /// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm. + /// The algorithm works by maintaining two variables: + /// - maxSoFar: The maximum sum found so far (global maximum) + /// - maxEndingHere: The maximum sum of subarray ending at current position (local maximum) + /// At each position, we decide whether to extend the existing subarray or start a new one. + /// + /// The input array of integers. + /// The maximum sum of a contiguous subarray. + /// Thrown when the input array is null or empty. + /// + /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4] + /// Output: 6 (subarray [4, -1, 2, 1]) + /// + public static int FindMaximumSubarraySum(int[] array) + { + // Validate input to ensure array is not null or empty + if (array == null || array.Length == 0) + { + throw new ArgumentException("Array cannot be null or empty.", nameof(array)); + } + + // Initialize both variables with the first element + // maxSoFar tracks the best sum we've seen across all subarrays + int maxSoFar = array[0]; + // maxEndingHere tracks the best sum ending at the current position + int maxEndingHere = array[0]; + + // Iterate through the array starting from the second element + for (int i = 1; i < array.Length; i++) + { + // Key decision: Either extend the current subarray or start fresh + // If adding current element to existing sum is worse than the element alone, + // it's better to start a new subarray from current element + maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]); + + // Update the global maximum if current subarray sum is better + maxSoFar = Math.Max(maxSoFar, maxEndingHere); + } + + return maxSoFar; + } + + /// + /// Finds the maximum sum of a contiguous subarray and returns the start and end indices. + /// This variant tracks the indices of the maximum subarray in addition to the sum. + /// Useful when you need to know which elements form the maximum subarray. + /// + /// The input array of integers. + /// A tuple containing the maximum sum, start index, and end index. + /// Thrown when the input array is null or empty. + /// + /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4] + /// Output: (MaxSum: 6, StartIndex: 3, EndIndex: 6) + /// The subarray is [4, -1, 2, 1] + /// + public static (int MaxSum, int StartIndex, int EndIndex) FindMaximumSubarrayWithIndices(int[] array) + { + // Validate input + if (array == null || array.Length == 0) + { + throw new ArgumentException("Array cannot be null or empty.", nameof(array)); + } + + // Initialize tracking variables + int maxSoFar = array[0]; // Global maximum sum + int maxEndingHere = array[0]; // Local maximum sum ending at current position + int start = 0; // Start index of the maximum subarray + int end = 0; // End index of the maximum subarray + int tempStart = 0; // Temporary start index for current subarray + + // Process each element starting from index 1 + for (int i = 1; i < array.Length; i++) + { + // Decide whether to extend current subarray or start a new one + if (array[i] > maxEndingHere + array[i]) + { + // Starting fresh from current element is better + maxEndingHere = array[i]; + tempStart = i; // Mark this as potential start of new subarray + } + else + { + // Extending the current subarray is better + maxEndingHere = maxEndingHere + array[i]; + } + + // Update global maximum and indices if we found a better subarray + if (maxEndingHere > maxSoFar) + { + maxSoFar = maxEndingHere; + start = tempStart; // Commit the start index + end = i; // Current position is the end + } + } + + return (maxSoFar, start, end); + } + + /// + /// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm for long integers. + /// This overload handles larger numbers that exceed int range (up to 2^63 - 1). + /// The algorithm logic is identical to the int version but uses long arithmetic. + /// + /// The input array of long integers. + /// The maximum sum of a contiguous subarray. + /// Thrown when the input array is null or empty. + /// + /// Input: [1000000000L, -500000000L, 1000000000L] + /// Output: 1500000000L (entire array) + /// + public static long FindMaximumSubarraySum(long[] array) + { + // Validate input + if (array == null || array.Length == 0) + { + throw new ArgumentException("Array cannot be null or empty.", nameof(array)); + } + + // Initialize with first element (using long arithmetic) + long maxSoFar = array[0]; + long maxEndingHere = array[0]; + + // Apply Kadane's algorithm with long values + for (int i = 1; i < array.Length; i++) + { + // Decide: extend current subarray or start new one + maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]); + // Update global maximum + maxSoFar = Math.Max(maxSoFar, maxEndingHere); + } + + return maxSoFar; + } +} diff --git a/DataStructures.Tests/Deque/DequeTests.cs b/DataStructures.Tests/Deque/DequeTests.cs new file mode 100644 index 00000000..1652d7d9 --- /dev/null +++ b/DataStructures.Tests/Deque/DequeTests.cs @@ -0,0 +1,383 @@ +using DataStructures.Deque; +using NUnit.Framework; +using System; +using System.Linq; + +namespace DataStructures.Tests.Deque; + +/// +/// Comprehensive test suite for the Deque (Double-Ended Queue) data structure. +/// Tests cover: +/// - Constructor validation and initialization +/// - Basic operations (AddFront, AddRear, RemoveFront, RemoveRear) +/// - Peek operations (non-destructive reads) +/// - Edge cases (empty deque, single element, capacity overflow) +/// - Type flexibility (int, string, tuples) +/// - Circular array behavior and automatic resizing +/// - Mixed operations maintaining correct order +/// +public static class DequeTests +{ + [Test] + public static void Constructor_WithDefaultCapacity_CreatesEmptyDeque() + { + // Arrange & Act: Create deque with default capacity (16) + var deque = new Deque(); + + // Assert: Should be empty initially + Assert.That(deque.Count, Is.EqualTo(0)); + Assert.That(deque.IsEmpty, Is.True); + } + + [Test] + public static void Constructor_WithSpecifiedCapacity_CreatesEmptyDeque() + { + // Arrange & Act + var deque = new Deque(10); + + // Assert + Assert.That(deque.Count, Is.EqualTo(0)); + Assert.That(deque.IsEmpty, Is.True); + } + + [Test] + public static void Constructor_WithInvalidCapacity_ThrowsArgumentException() + { + // Arrange, Act & Assert: Capacity must be at least 1 + // Zero capacity should throw + Assert.Throws(() => new Deque(0)); + // Negative capacity should throw + Assert.Throws(() => new Deque(-1)); + } + + [Test] + public static void AddFront_AddsElementToFront() + { + // Arrange + var deque = new Deque(); + + // Act: Add elements to front (each becomes new front) + deque.AddFront(1); // Deque: [1] + deque.AddFront(2); // Deque: [2, 1] + deque.AddFront(3); // Deque: [3, 2, 1] + + // Assert: Most recently added element should be at front + Assert.That(deque.Count, Is.EqualTo(3)); + Assert.That(deque.PeekFront(), Is.EqualTo(3)); + } + + [Test] + public static void AddRear_AddsElementToRear() + { + // Arrange + var deque = new Deque(); + + // Act + deque.AddRear(1); + deque.AddRear(2); + deque.AddRear(3); + + // Assert + Assert.That(deque.Count, Is.EqualTo(3)); + Assert.That(deque.PeekRear(), Is.EqualTo(3)); + } + + [Test] + public static void RemoveFront_RemovesAndReturnsElementFromFront() + { + // Arrange: Build deque [1, 2, 3] + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + deque.AddRear(3); + + // Act: Remove front element + int result = deque.RemoveFront(); + + // Assert: Should return 1 and deque becomes [2, 3] + Assert.That(result, Is.EqualTo(1)); + Assert.That(deque.Count, Is.EqualTo(2)); + Assert.That(deque.PeekFront(), Is.EqualTo(2)); + } + + [Test] + public static void RemoveRear_RemovesAndReturnsElementFromRear() + { + // Arrange + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + deque.AddRear(3); + + // Act + int result = deque.RemoveRear(); + + // Assert + Assert.That(result, Is.EqualTo(3)); + Assert.That(deque.Count, Is.EqualTo(2)); + Assert.That(deque.PeekRear(), Is.EqualTo(2)); + } + + [Test] + public static void RemoveFront_OnEmptyDeque_ThrowsInvalidOperationException() + { + // Arrange: Create empty deque + var deque = new Deque(); + + // Act & Assert: Cannot remove from empty deque + Assert.Throws(() => deque.RemoveFront()); + } + + [Test] + public static void RemoveRear_OnEmptyDeque_ThrowsInvalidOperationException() + { + // Arrange + var deque = new Deque(); + + // Act & Assert + Assert.Throws(() => deque.RemoveRear()); + } + + [Test] + public static void PeekFront_ReturnsElementWithoutRemoving() + { + // Arrange + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + + // Act + int result = deque.PeekFront(); + + // Assert + Assert.That(result, Is.EqualTo(1)); + Assert.That(deque.Count, Is.EqualTo(2)); + } + + [Test] + public static void PeekRear_ReturnsElementWithoutRemoving() + { + // Arrange + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + + // Act + int result = deque.PeekRear(); + + // Assert + Assert.That(result, Is.EqualTo(2)); + Assert.That(deque.Count, Is.EqualTo(2)); + } + + [Test] + public static void PeekFront_OnEmptyDeque_ThrowsInvalidOperationException() + { + // Arrange + var deque = new Deque(); + + // Act & Assert + Assert.Throws(() => deque.PeekFront()); + } + + [Test] + public static void PeekRear_OnEmptyDeque_ThrowsInvalidOperationException() + { + // Arrange + var deque = new Deque(); + + // Act & Assert + Assert.Throws(() => deque.PeekRear()); + } + + [Test] + public static void Clear_RemovesAllElements() + { + // Arrange + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + deque.AddRear(3); + + // Act + deque.Clear(); + + // Assert + Assert.That(deque.Count, Is.EqualTo(0)); + Assert.That(deque.IsEmpty, Is.True); + } + + [Test] + public static void ToArray_ReturnsElementsInCorrectOrder() + { + // Arrange + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + deque.AddRear(3); + + // Act + int[] result = deque.ToArray(); + + // Assert + Assert.That(result, Is.EqualTo(new[] { 1, 2, 3 })); + } + + [Test] + public static void ToArray_WithMixedOperations_ReturnsCorrectOrder() + { + // Arrange: Build deque using both front and rear operations + var deque = new Deque(); + deque.AddFront(2); // Deque: [2] + deque.AddFront(1); // Deque: [1, 2] + deque.AddRear(3); // Deque: [1, 2, 3] + deque.AddRear(4); // Deque: [1, 2, 3, 4] + + // Act + int[] result = deque.ToArray(); + + // Assert: Array should maintain front-to-rear order + Assert.That(result, Is.EqualTo(new[] { 1, 2, 3, 4 })); + } + + [Test] + public static void Contains_WithExistingElement_ReturnsTrue() + { + // Arrange + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + deque.AddRear(3); + + // Act + bool result = deque.Contains(2); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public static void Contains_WithNonExistingElement_ReturnsFalse() + { + // Arrange + var deque = new Deque(); + deque.AddRear(1); + deque.AddRear(2); + deque.AddRear(3); + + // Act + bool result = deque.Contains(5); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public static void Deque_WithStringType_WorksCorrectly() + { + // Arrange + var deque = new Deque(); + + // Act + deque.AddRear("Hello"); + deque.AddFront("World"); + deque.AddRear("!"); + + // Assert + Assert.That(deque.Count, Is.EqualTo(3)); + Assert.That(deque.PeekFront(), Is.EqualTo("World")); + Assert.That(deque.PeekRear(), Is.EqualTo("!")); + } + + [Test] + public static void Deque_AutomaticallyResizes_WhenCapacityExceeded() + { + // Arrange: Create deque with small capacity of 2 + var deque = new Deque(2); + + // Act: Add more elements than initial capacity + deque.AddRear(1); // Capacity: 2, Count: 1 + deque.AddRear(2); // Capacity: 2, Count: 2 (full) + deque.AddRear(3); // Should trigger resize to capacity 4, Count: 3 + deque.AddRear(4); // Capacity: 4, Count: 4 + + // Assert: All elements should be present in correct order + Assert.That(deque.Count, Is.EqualTo(4)); + Assert.That(deque.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4 })); + } + + [Test] + public static void Deque_MixedOperations_MaintainsCorrectOrder() + { + // Arrange + var deque = new Deque(); + + // Act: Perform complex sequence of operations + deque.AddRear(3); // Deque: [3] + deque.AddFront(2); // Deque: [2, 3] + deque.AddFront(1); // Deque: [1, 2, 3] + deque.AddRear(4); // Deque: [1, 2, 3, 4] + deque.RemoveFront(); // Deque: [2, 3, 4] (removed 1) + deque.RemoveRear(); // Deque: [2, 3] (removed 4) + deque.AddRear(5); // Deque: [2, 3, 5] + + // Assert: Final order should be correct after all operations + Assert.That(deque.ToArray(), Is.EqualTo(new[] { 2, 3, 5 })); + } + + [Test] + public static void Deque_WithComplexType_WorksCorrectly() + { + // Arrange + var deque = new Deque<(int, string)>(); + + // Act + deque.AddRear((1, "One")); + deque.AddRear((2, "Two")); + deque.AddFront((0, "Zero")); + + // Assert + Assert.That(deque.Count, Is.EqualTo(3)); + Assert.That(deque.PeekFront(), Is.EqualTo((0, "Zero"))); + Assert.That(deque.PeekRear(), Is.EqualTo((2, "Two"))); + } + + [Test] + public static void Deque_AfterMultipleResizes_MaintainsIntegrity() + { + // Arrange: Start with very small capacity + var deque = new Deque(2); + + // Act: Add many elements to trigger multiple resizes + // Capacity progression: 2 -> 4 -> 8 -> 16 -> 32 -> 64 -> 128 + for (int i = 0; i < 100; i++) + { + deque.AddRear(i); + } + + // Assert: All elements should be intact after multiple resizes + Assert.That(deque.Count, Is.EqualTo(100)); + Assert.That(deque.PeekFront(), Is.EqualTo(0)); + Assert.That(deque.PeekRear(), Is.EqualTo(99)); + Assert.That(deque.ToArray(), Is.EqualTo(Enumerable.Range(0, 100).ToArray())); + } + + [Test] + public static void Deque_CircularBehavior_WorksCorrectly() + { + // Arrange: Create deque with capacity 4 + var deque = new Deque(4); + + // Act: Test circular wrap-around behavior + deque.AddRear(1); // Internal: [1, _, _, _], front=0, rear=1 + deque.AddRear(2); // Internal: [1, 2, _, _], front=0, rear=2 + deque.RemoveFront(); // Internal: [_, 2, _, _], front=1, rear=2 + deque.RemoveFront(); // Internal: [_, _, _, _], front=2, rear=2 + deque.AddRear(3); // Internal: [_, _, 3, _], front=2, rear=3 + deque.AddRear(4); // Internal: [_, _, 3, 4], front=2, rear=0 (wrapped) + deque.AddRear(5); // Internal: [5, _, 3, 4], front=2, rear=1 (wrapped) + + // Assert: Elements should be in correct logical order despite circular storage + Assert.That(deque.ToArray(), Is.EqualTo(new[] { 3, 4, 5 })); + } +} diff --git a/DataStructures/Deque/Deque.cs b/DataStructures/Deque/Deque.cs new file mode 100644 index 00000000..99a42ed7 --- /dev/null +++ b/DataStructures/Deque/Deque.cs @@ -0,0 +1,324 @@ +namespace DataStructures.Deque; + +/// +/// 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. +/// +/// The type of elements in the deque. +public class Deque +{ + // 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; + + /// + /// Initializes a new instance of the 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. + /// + public Deque() : this(16) + { + } + + /// + /// Initializes a new instance of the class with specified capacity. + /// + /// The initial capacity of the deque. + /// Thrown when capacity is less than 1. + 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; + } + + /// + /// Gets the number of elements in the deque. + /// + public int Count => count; + + /// + /// Gets a value indicating whether the deque is empty. + /// + public bool IsEmpty => count == 0; + + /// + /// Adds an element to the front of the deque. + /// This operation is O(1) time complexity (amortized due to occasional resizing). + /// + /// The item to add. + /// + /// deque.AddFront(5); // Deque: [5] + /// deque.AddFront(3); // Deque: [3, 5] + /// + 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++; + } + + /// + /// Adds an element to the rear of the deque. + /// This operation is O(1) time complexity (amortized due to occasional resizing). + /// + /// The item to add. + /// + /// deque.AddRear(5); // Deque: [5] + /// deque.AddRear(7); // Deque: [5, 7] + /// + 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++; + } + + /// + /// Removes and returns the element at the front of the deque. + /// This operation is O(1) time complexity. + /// + /// The element at the front of the deque. + /// Thrown when the deque is empty. + /// + /// // Deque: [3, 5, 7] + /// int value = deque.RemoveFront(); // Returns 3, Deque: [5, 7] + /// + 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; + } + + /// + /// Removes and returns the element at the rear of the deque. + /// This operation is O(1) time complexity. + /// + /// The element at the rear of the deque. + /// Thrown when the deque is empty. + /// + /// // Deque: [3, 5, 7] + /// int value = deque.RemoveRear(); // Returns 7, Deque: [3, 5] + /// + 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; + } + + /// + /// 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. + /// + /// The element at the front of the deque. + /// Thrown when the deque is empty. + /// + /// // Deque: [3, 5, 7] + /// int value = deque.PeekFront(); // Returns 3, Deque unchanged: [3, 5, 7] + /// + public T PeekFront() + { + // Validate that deque is not empty + if (IsEmpty) + { + throw new InvalidOperationException("Deque is empty."); + } + + return items[front]; + } + + /// + /// 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. + /// + /// The element at the rear of the deque. + /// Thrown when the deque is empty. + /// + /// // Deque: [3, 5, 7] + /// int value = deque.PeekRear(); // Returns 7, Deque unchanged: [3, 5, 7] + /// + 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]; + } + + /// + /// 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. + /// + 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; + } + + /// + /// 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. + /// + /// An array containing all elements in the deque from front to rear. + /// + /// // Deque: [3, 5, 7] + /// int[] array = deque.ToArray(); // Returns [3, 5, 7] + /// + 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; + } + + /// + /// 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. + /// + /// The item to locate in the deque. + /// true if the item is found; otherwise, false. + /// + /// // Deque: [3, 5, 7] + /// bool exists = deque.Contains(5); // Returns true + /// bool missing = deque.Contains(9); // Returns false + /// + 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.Default.Equals(items[index], item)) + { + return true; + } + + // Move to next element in circular array + index = (index + 1) % items.Length; + } + + return false; + } + + /// + /// 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. + /// + 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; + } +} From 512cf7bde0541a660685ea19bd48e8b959bbca66 Mon Sep 17 00:00:00 2001 From: codomposer Date: Tue, 4 Nov 2025 10:47:21 -0500 Subject: [PATCH 2/3] made corresponding changes to the README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0773c264..8b499683 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ find more than one implementation for the same objective but using different alg * [Other](./Algorithms/Other) * [Fermat Prime Checker](./Algorithms/Other/FermatPrimeChecker.cs) * [Sieve of Eratosthenes](./Algorithms/Other/SieveOfEratosthenes.cs) + * [Kadane's Algorithm](./Algorithms/Other/KadanesAlgorithm.cs) * [Luhn](./Algorithms/Other/Luhn.cs) * [Int2Binary](./Algorithms/Other/Int2Binary.cs) * [GeoLocation](./Algorithms/Other/GeoLocation.cs) @@ -260,6 +261,7 @@ find more than one implementation for the same objective but using different alg * [Data Structures](./DataStructures) * [Bag](./DataStructures/Bag) * [Bit Array](./DataStructures/BitArray.cs) + * [Deque (Double-Ended Queue)](./DataStructures/Deque/Deque.cs) * [Timeline](./DataStructures/Timeline.cs) * [Segment Trees](./DataStructures/SegmentTrees) * [Segment Tree](./DataStructures/SegmentTrees/SegmentTree.cs) From 9ae60e80ca65d8932ecf4010a166659ccc5753a2 Mon Sep 17 00:00:00 2001 From: codomposer Date: Tue, 4 Nov 2025 11:11:47 -0500 Subject: [PATCH 3/3] fix: resolve StyleCop violations (SA1515, SA1028, SA1128, SA1629) --- Algorithms/Other/KadanesAlgorithm.cs | 18 +++++---- DataStructures/Deque/Deque.cs | 56 ++++++++++++++++------------ 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/Algorithms/Other/KadanesAlgorithm.cs b/Algorithms/Other/KadanesAlgorithm.cs index 4050996e..67287d27 100644 --- a/Algorithms/Other/KadanesAlgorithm.cs +++ b/Algorithms/Other/KadanesAlgorithm.cs @@ -19,8 +19,8 @@ public static class KadanesAlgorithm /// The maximum sum of a contiguous subarray. /// Thrown when the input array is null or empty. /// - /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4] - /// Output: 6 (subarray [4, -1, 2, 1]) + /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4]. + /// Output: 6 (subarray [4, -1, 2, 1]). /// public static int FindMaximumSubarraySum(int[] array) { @@ -33,6 +33,7 @@ public static int FindMaximumSubarraySum(int[] array) // Initialize both variables with the first element // maxSoFar tracks the best sum we've seen across all subarrays int maxSoFar = array[0]; + // maxEndingHere tracks the best sum ending at the current position int maxEndingHere = array[0]; @@ -43,7 +44,7 @@ public static int FindMaximumSubarraySum(int[] array) // If adding current element to existing sum is worse than the element alone, // it's better to start a new subarray from current element maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]); - + // Update the global maximum if current subarray sum is better maxSoFar = Math.Max(maxSoFar, maxEndingHere); } @@ -60,9 +61,9 @@ public static int FindMaximumSubarraySum(int[] array) /// A tuple containing the maximum sum, start index, and end index. /// Thrown when the input array is null or empty. /// - /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4] - /// Output: (MaxSum: 6, StartIndex: 3, EndIndex: 6) - /// The subarray is [4, -1, 2, 1] + /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4]. + /// Output: (MaxSum: 6, StartIndex: 3, EndIndex: 6). + /// The subarray is [4, -1, 2, 1]. /// public static (int MaxSum, int StartIndex, int EndIndex) FindMaximumSubarrayWithIndices(int[] array) { @@ -116,8 +117,8 @@ public static (int MaxSum, int StartIndex, int EndIndex) FindMaximumSubarrayWith /// The maximum sum of a contiguous subarray. /// Thrown when the input array is null or empty. /// - /// Input: [1000000000L, -500000000L, 1000000000L] - /// Output: 1500000000L (entire array) + /// Input: [1000000000L, -500000000L, 1000000000L]. + /// Output: 1500000000L (entire array). /// public static long FindMaximumSubarraySum(long[] array) { @@ -136,6 +137,7 @@ public static long FindMaximumSubarraySum(long[] array) { // Decide: extend current subarray or start new one maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]); + // Update global maximum maxSoFar = Math.Max(maxSoFar, maxEndingHere); } diff --git a/DataStructures/Deque/Deque.cs b/DataStructures/Deque/Deque.cs index 99a42ed7..48544ad8 100644 --- a/DataStructures/Deque/Deque.cs +++ b/DataStructures/Deque/Deque.cs @@ -4,19 +4,19 @@ namespace DataStructures.Deque; /// 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. /// /// The type of elements in the deque. @@ -24,13 +24,13 @@ public class Deque { // 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; @@ -39,7 +39,8 @@ public class Deque /// Default capacity is 16 elements, which provides a good balance between /// memory usage and avoiding early resizing for typical use cases. /// - public Deque() : this(16) + public Deque() + : this(16) { } @@ -77,8 +78,8 @@ public Deque(int capacity) /// /// The item to add. /// - /// deque.AddFront(5); // Deque: [5] - /// deque.AddFront(3); // Deque: [3, 5] + /// deque.AddFront(5); // Deque: [5]. + /// deque.AddFront(3); // Deque: [3, 5]. /// public void AddFront(T item) { @@ -101,8 +102,8 @@ public void AddFront(T item) /// /// The item to add. /// - /// deque.AddRear(5); // Deque: [5] - /// deque.AddRear(7); // Deque: [5, 7] + /// deque.AddRear(5); // Deque: [5]. + /// deque.AddRear(7); // Deque: [5, 7]. /// public void AddRear(T item) { @@ -114,6 +115,7 @@ public void AddRear(T item) // Add item at rear position items[rear] = item; + // Move rear pointer forward in circular fashion rear = (rear + 1) % items.Length; count++; @@ -126,8 +128,8 @@ public void AddRear(T item) /// The element at the front of the deque. /// Thrown when the deque is empty. /// - /// // Deque: [3, 5, 7] - /// int value = deque.RemoveFront(); // Returns 3, Deque: [5, 7] + /// // Deque: [3, 5, 7]. + /// int value = deque.RemoveFront(); // Returns 3, Deque: [5, 7]. /// public T RemoveFront() { @@ -139,8 +141,10 @@ public T RemoveFront() // 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--; @@ -155,8 +159,8 @@ public T RemoveFront() /// The element at the rear of the deque. /// Thrown when the deque is empty. /// - /// // Deque: [3, 5, 7] - /// int value = deque.RemoveRear(); // Returns 7, Deque: [3, 5] + /// // Deque: [3, 5, 7]. + /// int value = deque.RemoveRear(); // Returns 7, Deque: [3, 5]. /// public T RemoveRear() { @@ -168,8 +172,10 @@ public T RemoveRear() // 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--; @@ -184,8 +190,8 @@ public T RemoveRear() /// The element at the front of the deque. /// Thrown when the deque is empty. /// - /// // Deque: [3, 5, 7] - /// int value = deque.PeekFront(); // Returns 3, Deque unchanged: [3, 5, 7] + /// // Deque: [3, 5, 7]. + /// int value = deque.PeekFront(); // Returns 3, Deque unchanged: [3, 5, 7]. /// public T PeekFront() { @@ -205,8 +211,8 @@ public T PeekFront() /// The element at the rear of the deque. /// Thrown when the deque is empty. /// - /// // Deque: [3, 5, 7] - /// int value = deque.PeekRear(); // Returns 7, Deque unchanged: [3, 5, 7] + /// // Deque: [3, 5, 7]. + /// int value = deque.PeekRear(); // Returns 7, Deque unchanged: [3, 5, 7]. /// public T PeekRear() { @@ -230,6 +236,7 @@ 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; @@ -243,8 +250,8 @@ public void Clear() /// /// An array containing all elements in the deque from front to rear. /// - /// // Deque: [3, 5, 7] - /// int[] array = deque.ToArray(); // Returns [3, 5, 7] + /// // Deque: [3, 5, 7]. + /// int[] array = deque.ToArray(); // Returns [3, 5, 7]. /// public T[] ToArray() { @@ -270,9 +277,9 @@ public T[] ToArray() /// The item to locate in the deque. /// true if the item is found; otherwise, false. /// - /// // Deque: [3, 5, 7] - /// bool exists = deque.Contains(5); // Returns true - /// bool missing = deque.Contains(9); // Returns false + /// // Deque: [3, 5, 7]. + /// bool exists = deque.Contains(5); // Returns true. + /// bool missing = deque.Contains(9); // Returns false. /// public bool Contains(T item) { @@ -317,6 +324,7 @@ private void Resize() // Replace old array with new larger array items = newItems; + // Reset pointers: front at 0, rear at position after last element front = 0; rear = count;