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