diff --git a/SobolevNS/docs/data/task1_data_structures/phonebook.py b/SobolevNS/docs/data/task1_data_structures/phonebook.py new file mode 100644 index 0000000..a9b9185 --- /dev/null +++ b/SobolevNS/docs/data/task1_data_structures/phonebook.py @@ -0,0 +1,267 @@ +""" +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: все самопроверки пройдены")