forked from UNN/2026-rff_mp
256 lines
7.0 KiB
Python
256 lines
7.0 KiB
Python
"""Procedural phone book data structures for assignment 1.
|
|
|
|
The task explicitly asks to avoid classes, so every structure is represented
|
|
with plain dictionaries, lists and functions.
|
|
"""
|
|
|
|
|
|
def _make_ll_node(name, phone, next_node=None):
|
|
return {"name": name, "phone": phone, "next": next_node}
|
|
|
|
|
|
def ll_insert(head, name, phone):
|
|
"""Insert or update a record in a linked list, returning the head."""
|
|
if head is None:
|
|
return _make_ll_node(name, phone)
|
|
|
|
current = head
|
|
while current is not None:
|
|
if current["name"] == name:
|
|
current["phone"] = phone
|
|
return head
|
|
if current["next"] is None:
|
|
break
|
|
current = current["next"]
|
|
|
|
current["next"] = _make_ll_node(name, phone)
|
|
return head
|
|
|
|
|
|
def ll_find(head, name):
|
|
"""Return a phone by name or None if there is no such record."""
|
|
current = head
|
|
while current is not None:
|
|
if current["name"] == name:
|
|
return current["phone"]
|
|
current = current["next"]
|
|
return None
|
|
|
|
|
|
def ll_delete(head, name):
|
|
"""Delete a record by name, returning the possibly changed head."""
|
|
previous = None
|
|
current = head
|
|
|
|
while current is not None:
|
|
if current["name"] == name:
|
|
if previous is None:
|
|
return current["next"]
|
|
previous["next"] = current["next"]
|
|
return head
|
|
|
|
previous = current
|
|
current = current["next"]
|
|
|
|
return head
|
|
|
|
|
|
def ll_list_all(head):
|
|
"""Return all linked-list records sorted by name."""
|
|
records = []
|
|
current = head
|
|
while current is not None:
|
|
records.append((current["name"], current["phone"]))
|
|
current = current["next"]
|
|
return sorted(records, key=lambda item: item[0])
|
|
|
|
|
|
def create_hash_table(size=20011):
|
|
"""Create a fixed-size hash table with separate chaining."""
|
|
return [None for _ in range(size)]
|
|
|
|
|
|
def _hash_name(name, bucket_count):
|
|
"""Stable polynomial hash, unlike Python's randomized built-in hash()."""
|
|
value = 0
|
|
for char in name:
|
|
value = (value * 31 + ord(char)) % bucket_count
|
|
return value
|
|
|
|
|
|
def ht_insert(buckets, name, phone):
|
|
"""Insert or update a record in the hash table."""
|
|
index = _hash_name(name, len(buckets))
|
|
buckets[index] = ll_insert(buckets[index], name, phone)
|
|
|
|
|
|
def ht_find(buckets, name):
|
|
"""Return a phone by name or None if there is no such record."""
|
|
index = _hash_name(name, len(buckets))
|
|
return ll_find(buckets[index], name)
|
|
|
|
|
|
def ht_delete(buckets, name):
|
|
"""Delete a record by name if it exists."""
|
|
index = _hash_name(name, len(buckets))
|
|
buckets[index] = ll_delete(buckets[index], name)
|
|
|
|
|
|
def ht_list_all(buckets):
|
|
"""Return all hash-table records sorted by name."""
|
|
records = []
|
|
for head in buckets:
|
|
current = head
|
|
while current is not None:
|
|
records.append((current["name"], current["phone"]))
|
|
current = current["next"]
|
|
return sorted(records, key=lambda item: item[0])
|
|
|
|
|
|
def _make_bst_node(name, phone):
|
|
return {"name": name, "phone": phone, "left": None, "right": None}
|
|
|
|
|
|
def bst_insert(root, name, phone):
|
|
"""Insert or update a record in a binary search tree."""
|
|
if root is None:
|
|
return _make_bst_node(name, phone)
|
|
|
|
current = root
|
|
while True:
|
|
if name == current["name"]:
|
|
current["phone"] = phone
|
|
return root
|
|
|
|
if name < current["name"]:
|
|
if current["left"] is None:
|
|
current["left"] = _make_bst_node(name, phone)
|
|
return root
|
|
current = current["left"]
|
|
else:
|
|
if current["right"] is None:
|
|
current["right"] = _make_bst_node(name, phone)
|
|
return root
|
|
current = current["right"]
|
|
|
|
|
|
def bst_find(root, name):
|
|
"""Return a phone by name or None if there is no such record."""
|
|
current = root
|
|
while current is not None:
|
|
if name == current["name"]:
|
|
return current["phone"]
|
|
if name < current["name"]:
|
|
current = current["left"]
|
|
else:
|
|
current = current["right"]
|
|
return None
|
|
|
|
|
|
def _detach_min(node):
|
|
"""Detach the minimal node from a subtree and return (new_subtree, min)."""
|
|
parent = None
|
|
current = node
|
|
|
|
while current["left"] is not None:
|
|
parent = current
|
|
current = current["left"]
|
|
|
|
if parent is None:
|
|
return current["right"], current
|
|
|
|
parent["left"] = current["right"]
|
|
current["right"] = None
|
|
return node, current
|
|
|
|
|
|
def bst_delete(root, name):
|
|
"""Delete a record from the tree, returning the possibly changed root."""
|
|
parent = None
|
|
current = root
|
|
|
|
while current is not None and current["name"] != name:
|
|
parent = current
|
|
if name < current["name"]:
|
|
current = current["left"]
|
|
else:
|
|
current = current["right"]
|
|
|
|
if current is None:
|
|
return root
|
|
|
|
if current["left"] is None:
|
|
replacement = current["right"]
|
|
elif current["right"] is None:
|
|
replacement = current["left"]
|
|
else:
|
|
new_right, successor = _detach_min(current["right"])
|
|
successor["left"] = current["left"]
|
|
successor["right"] = new_right
|
|
replacement = successor
|
|
|
|
if parent is None:
|
|
return replacement
|
|
|
|
if parent["left"] is current:
|
|
parent["left"] = replacement
|
|
else:
|
|
parent["right"] = replacement
|
|
|
|
return root
|
|
|
|
|
|
def bst_list_all(root):
|
|
"""Return all BST records sorted by name using in-order traversal."""
|
|
records = []
|
|
stack = []
|
|
current = root
|
|
|
|
while current is not None or stack:
|
|
while current is not None:
|
|
stack.append(current)
|
|
current = current["left"]
|
|
|
|
current = stack.pop()
|
|
records.append((current["name"], current["phone"]))
|
|
current = current["right"]
|
|
|
|
return records
|
|
|
|
|
|
def _assert_basic_operations():
|
|
records = [("Boris", "222"), ("Anna", "111"), ("Denis", "444")]
|
|
expected_sorted = [("Anna", "111"), ("Boris", "222"), ("Denis", "444")]
|
|
|
|
head = None
|
|
for name, phone in records:
|
|
head = ll_insert(head, name, phone)
|
|
assert ll_find(head, "Anna") == "111"
|
|
head = ll_insert(head, "Anna", "333")
|
|
assert ll_find(head, "Anna") == "333"
|
|
head = ll_delete(head, "Anna")
|
|
assert ll_find(head, "Anna") is None
|
|
assert ll_list_all(head) == [("Boris", "222"), ("Denis", "444")]
|
|
|
|
table = create_hash_table(17)
|
|
for name, phone in records:
|
|
ht_insert(table, name, phone)
|
|
assert ht_find(table, "Denis") == "444"
|
|
ht_insert(table, "Denis", "555")
|
|
assert ht_find(table, "Denis") == "555"
|
|
ht_delete(table, "Missing")
|
|
assert ("Anna", "111") in ht_list_all(table)
|
|
|
|
root = None
|
|
for name, phone in records:
|
|
root = bst_insert(root, name, phone)
|
|
assert bst_list_all(root) == expected_sorted
|
|
root = bst_delete(root, "Boris")
|
|
assert bst_find(root, "Boris") is None
|
|
assert bst_list_all(root) == [("Anna", "111"), ("Denis", "444")]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
_assert_basic_operations()
|
|
print("All phonebook checks passed.")
|