Initial commit: Randomized Quicksort and Hash Table with Chaining implementation
- Implemented Randomized Quicksort algorithm with performance analysis - Implemented Hash Table with Chaining for collision resolution - Added comprehensive test suite (30+ test cases) - Created test runner script with multiple test options - Added detailed README with architecture diagrams and documentation - Added MIT License - Includes examples and comprehensive documentation
This commit is contained in:
4
tests/__init__.py
Normal file
4
tests/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
Test suite for MSCS532 Assignment 3 implementations.
|
||||
"""
|
||||
|
||||
218
tests/test_hash_table.py
Normal file
218
tests/test_hash_table.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Unit tests for Hash Table with Chaining implementation.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from src.hash_table import HashTable, HashNode
|
||||
|
||||
|
||||
class TestHashTable(unittest.TestCase):
|
||||
"""Test cases for hash table with chaining."""
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test hash table initialization."""
|
||||
ht = HashTable(initial_size=16)
|
||||
self.assertEqual(ht.size, 16)
|
||||
self.assertEqual(len(ht), 0)
|
||||
self.assertEqual(ht.get_load_factor(), 0.0)
|
||||
|
||||
def test_insert_and_get(self):
|
||||
"""Test basic insert and get operations."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
ht.insert(2, "banana")
|
||||
|
||||
self.assertEqual(ht.get(1), "apple")
|
||||
self.assertEqual(ht.get(2), "banana")
|
||||
self.assertEqual(len(ht), 2)
|
||||
|
||||
def test_insert_update(self):
|
||||
"""Test that inserting same key updates value."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
ht.insert(1, "banana")
|
||||
|
||||
self.assertEqual(ht.get(1), "banana")
|
||||
self.assertEqual(len(ht), 1)
|
||||
|
||||
def test_get_nonexistent_key(self):
|
||||
"""Test getting a key that doesn't exist."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
|
||||
self.assertIsNone(ht.get(2))
|
||||
|
||||
def test_delete_existing_key(self):
|
||||
"""Test deleting an existing key."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
ht.insert(2, "banana")
|
||||
|
||||
deleted = ht.delete(1)
|
||||
self.assertTrue(deleted)
|
||||
self.assertIsNone(ht.get(1))
|
||||
self.assertEqual(ht.get(2), "banana")
|
||||
self.assertEqual(len(ht), 1)
|
||||
|
||||
def test_delete_nonexistent_key(self):
|
||||
"""Test deleting a key that doesn't exist."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
|
||||
deleted = ht.delete(2)
|
||||
self.assertFalse(deleted)
|
||||
self.assertEqual(len(ht), 1)
|
||||
|
||||
def test_contains(self):
|
||||
"""Test contains method."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
|
||||
self.assertTrue(ht.contains(1))
|
||||
self.assertFalse(ht.contains(2))
|
||||
|
||||
def test_in_operator(self):
|
||||
"""Test using 'in' operator."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
|
||||
self.assertIn(1, ht)
|
||||
self.assertNotIn(2, ht)
|
||||
|
||||
def test_load_factor(self):
|
||||
"""Test load factor calculation."""
|
||||
ht = HashTable(initial_size=4)
|
||||
|
||||
# Initially empty
|
||||
self.assertEqual(ht.get_load_factor(), 0.0)
|
||||
|
||||
# Add elements
|
||||
ht.insert(1, "a")
|
||||
self.assertEqual(ht.get_load_factor(), 0.25)
|
||||
|
||||
ht.insert(2, "b")
|
||||
self.assertEqual(ht.get_load_factor(), 0.5)
|
||||
|
||||
ht.insert(3, "c")
|
||||
self.assertEqual(ht.get_load_factor(), 0.75)
|
||||
|
||||
def test_resize(self):
|
||||
"""Test automatic resizing when load factor threshold is reached."""
|
||||
ht = HashTable(initial_size=4, load_factor_threshold=0.75)
|
||||
|
||||
# Insert elements to trigger resize
|
||||
ht.insert(1, "a")
|
||||
ht.insert(2, "b")
|
||||
ht.insert(3, "c")
|
||||
# This should trigger resize (3/4 = 0.75)
|
||||
ht.insert(4, "d")
|
||||
|
||||
# Size should have doubled
|
||||
self.assertEqual(ht.size, 8)
|
||||
|
||||
# All elements should still be accessible
|
||||
self.assertEqual(ht.get(1), "a")
|
||||
self.assertEqual(ht.get(2), "b")
|
||||
self.assertEqual(ht.get(3), "c")
|
||||
self.assertEqual(ht.get(4), "d")
|
||||
self.assertEqual(len(ht), 4)
|
||||
|
||||
def test_get_all_items(self):
|
||||
"""Test getting all items from hash table."""
|
||||
ht = HashTable()
|
||||
ht.insert(1, "apple")
|
||||
ht.insert(2, "banana")
|
||||
ht.insert(3, "cherry")
|
||||
|
||||
items = ht.get_all_items()
|
||||
self.assertEqual(len(items), 3)
|
||||
|
||||
# Check that all items are present
|
||||
item_dict = dict(items)
|
||||
self.assertEqual(item_dict[1], "apple")
|
||||
self.assertEqual(item_dict[2], "banana")
|
||||
self.assertEqual(item_dict[3], "cherry")
|
||||
|
||||
def test_collision_handling(self):
|
||||
"""Test that collisions are handled correctly."""
|
||||
ht = HashTable(initial_size=5)
|
||||
|
||||
# Insert keys that might collide
|
||||
keys = [1, 6, 11, 16, 21]
|
||||
for key in keys:
|
||||
ht.insert(key, f"value_{key}")
|
||||
|
||||
# All keys should be retrievable
|
||||
for key in keys:
|
||||
self.assertEqual(ht.get(key), f"value_{key}")
|
||||
|
||||
self.assertEqual(len(ht), len(keys))
|
||||
|
||||
def test_delete_from_chain(self):
|
||||
"""Test deleting an element from the middle of a chain."""
|
||||
ht = HashTable(initial_size=5)
|
||||
|
||||
# Create a chain by inserting colliding keys
|
||||
keys = [1, 6, 11]
|
||||
for key in keys:
|
||||
ht.insert(key, f"value_{key}")
|
||||
|
||||
# Delete middle element
|
||||
deleted = ht.delete(6)
|
||||
self.assertTrue(deleted)
|
||||
|
||||
# Remaining elements should still be accessible
|
||||
self.assertEqual(ht.get(1), "value_1")
|
||||
self.assertIsNone(ht.get(6))
|
||||
self.assertEqual(ht.get(11), "value_11")
|
||||
self.assertEqual(len(ht), 2)
|
||||
|
||||
def test_len(self):
|
||||
"""Test __len__ method."""
|
||||
ht = HashTable()
|
||||
self.assertEqual(len(ht), 0)
|
||||
|
||||
ht.insert(1, "a")
|
||||
self.assertEqual(len(ht), 1)
|
||||
|
||||
ht.insert(2, "b")
|
||||
self.assertEqual(len(ht), 2)
|
||||
|
||||
ht.delete(1)
|
||||
self.assertEqual(len(ht), 1)
|
||||
|
||||
def test_multiple_operations(self):
|
||||
"""Test a sequence of mixed operations."""
|
||||
ht = HashTable()
|
||||
|
||||
# Insert
|
||||
ht.insert(1, "one")
|
||||
ht.insert(2, "two")
|
||||
ht.insert(3, "three")
|
||||
|
||||
# Update
|
||||
ht.insert(2, "TWO")
|
||||
|
||||
# Delete
|
||||
ht.delete(1)
|
||||
|
||||
# Verify final state
|
||||
self.assertIsNone(ht.get(1))
|
||||
self.assertEqual(ht.get(2), "TWO")
|
||||
self.assertEqual(ht.get(3), "three")
|
||||
self.assertEqual(len(ht), 2)
|
||||
|
||||
def test_empty_hash_table(self):
|
||||
"""Test operations on empty hash table."""
|
||||
ht = HashTable()
|
||||
|
||||
self.assertIsNone(ht.get(1))
|
||||
self.assertFalse(ht.contains(1))
|
||||
self.assertFalse(ht.delete(1))
|
||||
self.assertEqual(ht.get_all_items(), [])
|
||||
self.assertEqual(len(ht), 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
150
tests/test_quicksort.py
Normal file
150
tests/test_quicksort.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Unit tests for Randomized Quicksort implementation.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import random
|
||||
from src.quicksort import (
|
||||
randomized_quicksort,
|
||||
partition,
|
||||
randomized_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)
|
||||
|
||||
|
||||
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'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user