2026-rff_mp/shahovaa/zadanie1/phonebook.py

256 lines
7.0 KiB
Python
Raw Normal View History

2026-05-19 19:11:31 +00:00
"""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.")