- Implemented deterministic quicksort (first element as pivot) - Added comprehensive empirical comparison between randomized and deterministic quicksort - Expanded test suite from 30+ to 41+ tests covering: * Deterministic quicksort tests * Algorithm comparison tests * Edge case tests * Worst-case scenario tests - Updated README with comparison study documentation - All 57 tests passing successfully
414 lines
15 KiB
Python
414 lines
15 KiB
Python
"""
|
|
Unit tests for Randomized and Deterministic Quicksort implementations.
|
|
"""
|
|
|
|
import unittest
|
|
import random
|
|
from src.quicksort import (
|
|
randomized_quicksort,
|
|
deterministic_quicksort,
|
|
partition,
|
|
randomized_partition,
|
|
deterministic_partition,
|
|
compare_with_builtin,
|
|
analyze_performance
|
|
)
|
|
|
|
|
|
class TestRandomizedQuicksort(unittest.TestCase):
|
|
"""Test cases for randomized quicksort algorithm."""
|
|
|
|
def test_empty_array(self):
|
|
"""Test sorting an empty array."""
|
|
arr = []
|
|
result = randomized_quicksort(arr)
|
|
self.assertEqual(result, [])
|
|
|
|
def test_single_element(self):
|
|
"""Test sorting an array with a single element."""
|
|
arr = [42]
|
|
result = randomized_quicksort(arr)
|
|
self.assertEqual(result, [42])
|
|
|
|
def test_sorted_array(self):
|
|
"""Test sorting an already sorted array."""
|
|
arr = [1, 2, 3, 4, 5]
|
|
result = randomized_quicksort(arr)
|
|
self.assertEqual(result, [1, 2, 3, 4, 5])
|
|
|
|
def test_reverse_sorted_array(self):
|
|
"""Test sorting a reverse sorted array."""
|
|
arr = [5, 4, 3, 2, 1]
|
|
result = randomized_quicksort(arr)
|
|
self.assertEqual(result, [1, 2, 3, 4, 5])
|
|
|
|
def test_random_array(self):
|
|
"""Test sorting a random array."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
result = randomized_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_duplicate_elements(self):
|
|
"""Test sorting an array with duplicate elements."""
|
|
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
|
|
result = randomized_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_negative_numbers(self):
|
|
"""Test sorting an array with negative numbers."""
|
|
arr = [-5, -2, -8, 1, 3, -1, 0]
|
|
result = randomized_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_large_array(self):
|
|
"""Test sorting a large array."""
|
|
arr = [random.randint(1, 10000) for _ in range(1000)]
|
|
result = randomized_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_original_array_not_modified(self):
|
|
"""Test that the original array is not modified."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
original = arr.copy()
|
|
randomized_quicksort(arr)
|
|
self.assertEqual(arr, original)
|
|
|
|
def test_all_same_elements(self):
|
|
"""Test sorting an array with all same elements."""
|
|
arr = [5, 5, 5, 5, 5]
|
|
result = randomized_quicksort(arr)
|
|
self.assertEqual(result, [5, 5, 5, 5, 5])
|
|
|
|
|
|
class TestPartition(unittest.TestCase):
|
|
"""Test cases for partition function."""
|
|
|
|
def test_partition(self):
|
|
"""Test partition function."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
pivot_idx = partition(arr, 0, len(arr) - 1)
|
|
|
|
# Check that pivot is in correct position
|
|
pivot_value = arr[pivot_idx]
|
|
# All elements before pivot should be <= pivot
|
|
for i in range(0, pivot_idx):
|
|
self.assertLessEqual(arr[i], pivot_value)
|
|
# All elements after pivot should be >= pivot
|
|
for i in range(pivot_idx + 1, len(arr)):
|
|
self.assertGreaterEqual(arr[i], pivot_value)
|
|
|
|
def test_randomized_partition(self):
|
|
"""Test randomized partition function."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
pivot_idx = randomized_partition(arr, 0, len(arr) - 1)
|
|
|
|
# Check that pivot is in correct position
|
|
pivot_value = arr[pivot_idx]
|
|
# All elements before pivot should be <= pivot
|
|
for i in range(0, pivot_idx):
|
|
self.assertLessEqual(arr[i], pivot_value)
|
|
# All elements after pivot should be >= pivot
|
|
for i in range(pivot_idx + 1, len(arr)):
|
|
self.assertGreaterEqual(arr[i], pivot_value)
|
|
|
|
def test_deterministic_partition(self):
|
|
"""Test deterministic partition function."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
pivot_idx = deterministic_partition(arr, 0, len(arr) - 1)
|
|
|
|
# Check that pivot is in correct position
|
|
pivot_value = arr[pivot_idx]
|
|
# All elements before pivot should be <= pivot
|
|
for i in range(0, pivot_idx):
|
|
self.assertLessEqual(arr[i], pivot_value)
|
|
# All elements after pivot should be >= pivot
|
|
for i in range(pivot_idx + 1, len(arr)):
|
|
self.assertGreaterEqual(arr[i], pivot_value)
|
|
|
|
|
|
class TestDeterministicQuicksort(unittest.TestCase):
|
|
"""Test cases for deterministic quicksort algorithm."""
|
|
|
|
def test_empty_array(self):
|
|
"""Test sorting an empty array."""
|
|
arr = []
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, [])
|
|
|
|
def test_single_element(self):
|
|
"""Test sorting an array with a single element."""
|
|
arr = [42]
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, [42])
|
|
|
|
def test_sorted_array(self):
|
|
"""Test sorting an already sorted array (worst case for deterministic)."""
|
|
arr = [1, 2, 3, 4, 5]
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, [1, 2, 3, 4, 5])
|
|
|
|
def test_reverse_sorted_array(self):
|
|
"""Test sorting a reverse sorted array (worst case for deterministic)."""
|
|
arr = [5, 4, 3, 2, 1]
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, [1, 2, 3, 4, 5])
|
|
|
|
def test_random_array(self):
|
|
"""Test sorting a random array."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_duplicate_elements(self):
|
|
"""Test sorting an array with duplicate elements."""
|
|
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
|
|
result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_negative_numbers(self):
|
|
"""Test sorting an array with negative numbers."""
|
|
arr = [-5, -2, -8, 1, 3, -1, 0]
|
|
result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_large_array(self):
|
|
"""Test sorting a large array."""
|
|
arr = [random.randint(1, 10000) for _ in range(1000)]
|
|
result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_original_array_not_modified(self):
|
|
"""Test that the original array is not modified."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
original = arr.copy()
|
|
deterministic_quicksort(arr)
|
|
self.assertEqual(arr, original)
|
|
|
|
def test_all_same_elements(self):
|
|
"""Test sorting an array with all same elements."""
|
|
arr = [5, 5, 5, 5, 5]
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, [5, 5, 5, 5, 5])
|
|
|
|
|
|
class TestQuicksortComparison(unittest.TestCase):
|
|
"""Test cases comparing randomized vs deterministic quicksort."""
|
|
|
|
def test_both_produce_same_result(self):
|
|
"""Test that both algorithms produce identical results."""
|
|
arr = [64, 34, 25, 12, 22, 11, 90, 5]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
self.assertEqual(rand_result, det_result)
|
|
|
|
def test_both_handle_empty_array(self):
|
|
"""Test both algorithms handle empty arrays."""
|
|
arr = []
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
|
|
self.assertEqual(rand_result, [])
|
|
self.assertEqual(det_result, [])
|
|
|
|
def test_both_handle_duplicates(self):
|
|
"""Test both algorithms handle duplicate elements."""
|
|
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
|
|
def test_both_handle_sorted_array(self):
|
|
"""Test both algorithms handle already sorted arrays."""
|
|
arr = [1, 2, 3, 4, 5]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
|
|
self.assertEqual(rand_result, arr)
|
|
self.assertEqual(det_result, arr)
|
|
|
|
def test_both_handle_reverse_sorted_array(self):
|
|
"""Test both algorithms handle reverse sorted arrays."""
|
|
arr = [5, 4, 3, 2, 1]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
|
|
def test_both_handle_negative_numbers(self):
|
|
"""Test both algorithms handle negative numbers."""
|
|
arr = [-5, -2, -8, 1, 3, -1, 0]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
|
|
def test_both_handle_large_array(self):
|
|
"""Test both algorithms handle large arrays."""
|
|
arr = [random.randint(1, 10000) for _ in range(1000)]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
|
|
def test_deterministic_worst_case_performance(self):
|
|
"""Test deterministic quicksort on worst-case inputs (sorted arrays)."""
|
|
# Small sorted array - should still work correctly
|
|
arr = list(range(1, 101)) # 100 elements
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, arr)
|
|
|
|
# Medium sorted array
|
|
arr = list(range(1, 501)) # 500 elements
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, arr)
|
|
|
|
def test_randomized_consistent_performance(self):
|
|
"""Test randomized quicksort maintains consistent performance."""
|
|
# Test on sorted array (worst case for deterministic)
|
|
arr = list(range(1, 101))
|
|
rand_result = randomized_quicksort(arr)
|
|
self.assertEqual(rand_result, arr)
|
|
|
|
# Test on reverse sorted array
|
|
arr = list(range(100, 0, -1))
|
|
rand_result = randomized_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(rand_result, expected)
|
|
|
|
# Test on random array
|
|
arr = [random.randint(1, 1000) for _ in range(100)]
|
|
rand_result = randomized_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(rand_result, expected)
|
|
|
|
|
|
class TestPerformanceComparison(unittest.TestCase):
|
|
"""Test cases for performance comparison utilities."""
|
|
|
|
def test_compare_with_builtin(self):
|
|
"""Test comparison with built-in sort."""
|
|
arr = [random.randint(1, 1000) for _ in range(100)]
|
|
comparison = compare_with_builtin(arr)
|
|
|
|
self.assertIn('quicksort_time', comparison)
|
|
self.assertIn('builtin_time', comparison)
|
|
self.assertIn('speedup', comparison)
|
|
self.assertIn('is_correct', comparison)
|
|
self.assertIn('array_length', comparison)
|
|
|
|
self.assertTrue(comparison['is_correct'])
|
|
self.assertEqual(comparison['array_length'], 100)
|
|
self.assertGreater(comparison['quicksort_time'], 0)
|
|
self.assertGreater(comparison['builtin_time'], 0)
|
|
|
|
def test_analyze_performance(self):
|
|
"""Test performance analysis."""
|
|
results = analyze_performance([100, 1000])
|
|
|
|
self.assertEqual(len(results), 2)
|
|
for result in results:
|
|
self.assertIn('quicksort_time', result)
|
|
self.assertIn('builtin_time', result)
|
|
self.assertIn('is_correct', result)
|
|
self.assertTrue(result['is_correct'])
|
|
|
|
|
|
class TestEdgeCases(unittest.TestCase):
|
|
"""Test cases for edge cases and boundary conditions."""
|
|
|
|
def test_zero_elements(self):
|
|
"""Test arrays with zero elements."""
|
|
arr = []
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
|
|
self.assertEqual(rand_result, [])
|
|
self.assertEqual(det_result, [])
|
|
|
|
def test_single_element(self):
|
|
"""Test arrays with single element."""
|
|
arr = [42]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
|
|
self.assertEqual(rand_result, [42])
|
|
self.assertEqual(det_result, [42])
|
|
|
|
def test_two_elements(self):
|
|
"""Test arrays with two elements."""
|
|
arr = [2, 1]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
|
|
def test_all_zeros(self):
|
|
"""Test arrays with all zeros."""
|
|
arr = [0, 0, 0, 0, 0]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
|
|
self.assertEqual(rand_result, arr)
|
|
self.assertEqual(det_result, arr)
|
|
|
|
def test_mixed_positive_negative(self):
|
|
"""Test arrays with mixed positive and negative numbers."""
|
|
arr = [-5, 10, -3, 0, 7, -1, 2]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
|
|
def test_large_range(self):
|
|
"""Test arrays with large value range."""
|
|
arr = [1, 1000000, 500000, 250000, 750000]
|
|
rand_result = randomized_quicksort(arr)
|
|
det_result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
|
|
self.assertEqual(rand_result, expected)
|
|
self.assertEqual(det_result, expected)
|
|
|
|
def test_deterministic_worst_case_small(self):
|
|
"""Test deterministic quicksort on small worst-case inputs."""
|
|
# Small sorted array
|
|
arr = list(range(1, 51))
|
|
result = deterministic_quicksort(arr)
|
|
self.assertEqual(result, arr)
|
|
|
|
# Small reverse sorted array
|
|
arr = list(range(50, 0, -1))
|
|
result = deterministic_quicksort(arr)
|
|
expected = sorted(arr)
|
|
self.assertEqual(result, expected)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
|