"""
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: все самопроверки пройдены")
