Files
MSCS532_Assignment7/tests/test_hash_tables.py
2025-11-24 17:11:51 -05:00

204 lines
6.6 KiB
Python

"""
Tests for hash table implementations.
"""
import pytest
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from src.hash_tables import (
DirectAddressTable,
HashTableOpenAddressing,
HashTableSeparateChaining
)
from src.hash_functions import division_hash
class TestDirectAddressTable:
"""Tests for direct-address table."""
def test_insert_and_search(self):
"""Test basic insert and search operations."""
table = DirectAddressTable(100)
table.insert(5, "value1")
table.insert(42, "value2")
assert table.search(5) == "value1"
assert table.search(42) == "value2"
assert table.search(10) is None
def test_delete(self):
"""Test delete operation."""
table = DirectAddressTable(100)
table.insert(5, "value1")
table.delete(5)
assert table.search(5) is None
def test_out_of_range_key(self):
"""Test handling of out-of-range keys."""
table = DirectAddressTable(100)
with pytest.raises(ValueError):
table.insert(100, "value") # Out of range
assert table.search(100) is None
class TestHashTableOpenAddressing:
"""Tests for open addressing hash table."""
def test_insert_and_search_linear(self):
"""Test insert and search with linear probing."""
ht = HashTableOpenAddressing(10, probe_type='linear')
ht.insert(10, "value1")
ht.insert(22, "value2")
ht.insert(31, "value3")
assert ht.search(10) == "value1"
assert ht.search(22) == "value2"
assert ht.search(31) == "value3"
assert ht.search(99) is None
def test_insert_and_search_quadratic(self):
"""Test insert and search with quadratic probing."""
ht = HashTableOpenAddressing(10, probe_type='quadratic')
ht.insert(10, "value1")
ht.insert(22, "value2")
assert ht.search(10) == "value1"
assert ht.search(22) == "value2"
def test_insert_and_search_double(self):
"""Test insert and search with double hashing."""
ht = HashTableOpenAddressing(10, probe_type='double')
ht.insert(10, "value1")
ht.insert(22, "value2")
assert ht.search(10) == "value1"
assert ht.search(22) == "value2"
def test_delete(self):
"""Test delete operation."""
ht = HashTableOpenAddressing(10, probe_type='linear')
ht.insert(10, "value1")
ht.insert(22, "value2")
assert ht.delete(10) is True
assert ht.search(10) is None
assert ht.search(22) == "value2"
assert ht.delete(99) is False
def test_update_existing_key(self):
"""Test updating an existing key."""
ht = HashTableOpenAddressing(10, probe_type='linear')
ht.insert(10, "value1")
ht.insert(10, "value2") # Update
assert ht.search(10) == "value2"
def test_resize(self):
"""Test automatic resizing."""
ht = HashTableOpenAddressing(5, probe_type='linear', load_factor_threshold=0.7)
# Insert enough to trigger resize
for i in range(10):
ht.insert(i, f"value{i}")
# All should still be searchable
for i in range(10):
assert ht.search(i) == f"value{i}"
class TestHashTableSeparateChaining:
"""Tests for separate chaining hash table."""
def test_insert_and_search(self):
"""Test basic insert and search operations."""
ht = HashTableSeparateChaining(10)
ht.insert(10, "value1")
ht.insert(22, "value2")
ht.insert(31, "value3")
assert ht.search(10) == "value1"
assert ht.search(22) == "value2"
assert ht.search(31) == "value3"
assert ht.search(99) is None
def test_delete(self):
"""Test delete operation."""
ht = HashTableSeparateChaining(10)
ht.insert(10, "value1")
ht.insert(22, "value2")
assert ht.delete(10) is True
assert ht.search(10) is None
assert ht.search(22) == "value2"
assert ht.delete(99) is False
def test_update_existing_key(self):
"""Test updating an existing key."""
ht = HashTableSeparateChaining(10)
ht.insert(10, "value1")
ht.insert(10, "value2") # Update
assert ht.search(10) == "value2"
def test_collision_handling(self):
"""Test that collisions are handled correctly."""
ht = HashTableSeparateChaining(5) # Small table to force collisions
keys = [10, 15, 20, 25, 30]
for key in keys:
ht.insert(key, f"value{key}")
# All should be searchable
for key in keys:
assert ht.search(key) == f"value{key}"
def test_chain_lengths(self):
"""Test chain length reporting."""
ht = HashTableSeparateChaining(5)
for i in range(10):
ht.insert(i, f"value{i}")
chain_lengths = ht.get_chain_lengths()
# After inserting 10 items, table will resize (load factor > 1.0)
# So chain lengths should match current table size, not initial size
assert len(chain_lengths) == ht.size
assert sum(chain_lengths) == 10
def test_resize(self):
"""Test automatic resizing."""
ht = HashTableSeparateChaining(5, load_factor_threshold=1.0)
# Insert enough to trigger resize
for i in range(20):
ht.insert(i, f"value{i}")
# All should still be searchable
for i in range(20):
assert ht.search(i) == f"value{i}"
class TestHashTableComparison:
"""Tests comparing different hash table implementations."""
def test_same_operations_different_implementations(self):
"""Test that different implementations handle same operations."""
keys = [10, 22, 31, 4, 15, 28, 17, 88, 59]
ht_oa = HashTableOpenAddressing(20, probe_type='linear')
ht_sc = HashTableSeparateChaining(20)
# Insert same keys
for key in keys:
ht_oa.insert(key, f"value{key}")
ht_sc.insert(key, f"value{key}")
# Both should find all keys
for key in keys:
assert ht_oa.search(key) == f"value{key}"
assert ht_sc.search(key) == f"value{key}"
# Both should delete successfully
for key in keys[:5]:
assert ht_oa.delete(key) is True
assert ht_sc.delete(key) is True
assert ht_oa.search(key) is None
assert ht_sc.search(key) is None