2026-rff_mp/shahovaa/zadanie1/phonebook.py
2026-05-19 22:11:31 +03:00

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.")