Adding hash algorithms
This commit is contained in:
203
tests/test_hash_tables.py
Normal file
203
tests/test_hash_tables.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user