2026-rff_mp/SobolevNS/docs/data/task1_data_structures/phonebook.py
2026-05-22 12:18:09 +03:00

268 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
phonebook.py
Три структуры данных для хранения телефонного справочника,
реализованные в процедурной парадигме (без классов).
Узлы и контейнеры представляются обычными словарями и списками.
"""
import sys
# Увеличиваем лимит рекурсии - нужно для BST в худшем случае
sys.setrecursionlimit(200_000)
# ============================================================
# 1. СВЯЗНЫЙ СПИСОК
# Узел: {'name': str, 'phone': str, 'next': dict|None}
# Голова списка - это либо узел, либо None (пустой список)
# ============================================================
def ll_create():
"""Создаёт пустой связный список."""
return None
def ll_insert(head, name, phone):
"""Вставляет или обновляет запись. Возвращает новую голову списка."""
# Если списка нет - создаём первый узел
if head is None:
return {'name': name, 'phone': phone, 'next': None}
# Если совпадение в голове - просто обновляем телефон
node = head
while node is not None:
if node['name'] == name:
node['phone'] = phone
return head
if node['next'] is None:
break
node = node['next']
# node - последний узел, добавляем после него
node['next'] = {'name': name, 'phone': phone, 'next': None}
return head
def ll_find(head, name):
"""Возвращает phone или None."""
node = head
while node is not None:
if node['name'] == name:
return node['phone']
node = node['next']
return None
def ll_delete(head, name):
"""Удаляет узел по имени. Возвращает новую голову (она могла измениться)."""
if head is None:
return None
# Удаление головы
if head['name'] == name:
return head['next']
prev = head
cur = head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return head
prev = cur
cur = cur['next']
# Не нашли - игнорируем
return head
def ll_collect(head):
"""Возвращает несортированный список (name, phone) - служебная функция."""
out = []
node = head
while node is not None:
out.append((node['name'], node['phone']))
node = node['next']
return out
def ll_list_all(head):
"""Возвращает все записи, отсортированные по имени."""
items = ll_collect(head)
items.sort(key=lambda x: x[0])
return items
# ============================================================
# 2. ХЕШ-ТАБЛИЦА
# buckets - список фиксированной длины из голов связных списков
# ============================================================
def ht_create(size=1024):
"""Создаёт пустую хеш-таблицу с заданным числом бакетов."""
return {
'size': size,
'buckets': [None] * size,
}
def _ht_index(name, size):
"""Хеш-функция: встроенный hash + остаток от деления."""
return hash(name) % size
def ht_insert(ht, name, phone):
"""Вставляет или обновляет запись в нужном бакете."""
idx = _ht_index(name, ht['size'])
ht['buckets'][idx] = ll_insert(ht['buckets'][idx], name, phone)
def ht_find(ht, name):
"""Возвращает phone или None."""
idx = _ht_index(name, ht['size'])
return ll_find(ht['buckets'][idx], name)
def ht_delete(ht, name):
"""Удаляет запись (если она есть)."""
idx = _ht_index(name, ht['size'])
ht['buckets'][idx] = ll_delete(ht['buckets'][idx], name)
def ht_list_all(ht):
"""Собирает все записи из всех бакетов и сортирует по имени."""
out = []
for head in ht['buckets']:
out.extend(ll_collect(head))
out.sort(key=lambda x: x[0])
return out
# ============================================================
# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА
# Узел: {'name': str, 'phone': str, 'left': dict|None, 'right': dict|None}
# Корень - узел или None
# ============================================================
def bst_create():
"""Создаёт пустое BST."""
return None
def bst_insert(root, name, phone):
"""Вставляет/обновляет. Итеративная реализация, чтобы не упереться в рекурсию
при отсортированном входе. Возвращает новый корень."""
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
cur = root
while True:
if name == cur['name']:
cur['phone'] = phone
return root
if name < cur['name']:
if cur['left'] is None:
cur['left'] = new_node
return root
cur = cur['left']
else:
if cur['right'] is None:
cur['right'] = new_node
return root
cur = cur['right']
def bst_find(root, name):
"""Возвращает phone или None. Итеративный поиск."""
cur = root
while cur is not None:
if name == cur['name']:
return cur['phone']
cur = cur['left'] if name < cur['name'] else cur['right']
return None
def _bst_min_node(node):
"""Возвращает узел с минимальным ключом в поддереве."""
while node['left'] is not None:
node = node['left']
return node
def bst_delete(root, name):
"""Удаление узла с возвратом нового корня. Стандартный алгоритм:
если у узла двое детей - заменяем его на минимальный из правого поддерева."""
if root is None:
return None
if name < root['name']:
root['left'] = bst_delete(root['left'], name)
elif name > root['name']:
root['right'] = bst_delete(root['right'], name)
else:
# Нашли узел для удаления
if root['left'] is None:
return root['right']
if root['right'] is None:
return root['left']
# Двое детей: берём преемника (мин. в правом поддереве)
successor = _bst_min_node(root['right'])
root['name'] = successor['name']
root['phone'] = successor['phone']
root['right'] = bst_delete(root['right'], successor['name'])
return root
def bst_list_all(root):
"""Центрированный обход (in-order) - сразу даёт отсортированный по имени список.
Итеративный, чтобы не упасть на вырожденном дереве."""
out = []
stack = []
cur = root
while cur is not None or stack:
while cur is not None:
stack.append(cur)
cur = cur['left']
cur = stack.pop()
out.append((cur['name'], cur['phone']))
cur = cur['right']
return out
# ============================================================
# Маленький self-test, запускается только при прямом вызове
# ============================================================
if __name__ == "__main__":
data = [("Alice", "111"), ("Bob", "222"), ("Charlie", "333"),
("Dave", "444"), ("Eve", "555")]
# LinkedList
head = ll_create()
for n, p in data:
head = ll_insert(head, n, p)
assert ll_find(head, "Bob") == "222"
assert ll_find(head, "Nope") is None
head = ll_delete(head, "Bob")
assert ll_find(head, "Bob") is None
assert ll_list_all(head) == sorted([(n, p) for n, p in data if n != "Bob"])
# HashTable
ht = ht_create(size=8)
for n, p in data:
ht_insert(ht, n, p)
assert ht_find(ht, "Charlie") == "333"
ht_delete(ht, "Charlie")
assert ht_find(ht, "Charlie") is None
assert ht_list_all(ht) == sorted([(n, p) for n, p in data if n != "Charlie"])
# BST
root = bst_create()
for n, p in data:
root = bst_insert(root, n, p)
assert bst_find(root, "Dave") == "444"
root = bst_delete(root, "Dave")
assert bst_find(root, "Dave") is None
assert bst_list_all(root) == sorted([(n, p) for n, p in data if n != "Dave"])
print("phonebook.py: все самопроверки пройдены")