# phonebook.py

# Узел списка: {'n': имя, 'p': телефон, 'nxt': следующий}
def ll_insert(head, name, phone):
    # обновление, если уже есть
    curr = head
    while curr is not None:
        if curr['n'] == name:
            curr['p'] = phone
            return head
        curr = curr['nxt']
    # вставка в начало (новый узел становится головой)
    new_node = {'n': name, 'p': phone, 'nxt': head}
    return new_node

def ll_find(head, name):
    curr = head
    while curr is not None:
        if curr['n'] == name:
            return curr['p']
        curr = curr['nxt']
    return None

def ll_delete(head, name):
    if head is None:
        return None
    if head['n'] == name:
        return head['nxt']
    prev = head
    curr = head['nxt']
    while curr is not None:
        if curr['n'] == name:
            prev['nxt'] = curr['nxt']
            return head
        prev = curr
        curr = curr['nxt']
    return head

def ll_list_all(head):
    records = []
    curr = head
    while curr is not None:
        records.append((curr['n'], curr['p']))
        curr = curr['nxt']
    records.sort(key=lambda x: x[0])
    return records







# хеш-функция: сумма ord(name) % size
def _hash(name, size):
    h = 0
    for ch in name:
        h += ord(ch)
    return h % size

SIZE = 13  # фиксированный размер таблицы

def ht_create():
    return [None] * SIZE

def ht_insert(buckets, name, phone):
    idx = _hash(name, len(buckets))
    buckets[idx] = ll_insert(buckets[idx], name, phone)
    return buckets

def ht_find(buckets, name):
    idx = _hash(name, len(buckets))
    return ll_find(buckets[idx], name)

def ht_delete(buckets, name):
    idx = _hash(name, len(buckets))
    buckets[idx] = ll_delete(buckets[idx], name)
    return buckets

def ht_list_all(buckets):
    all_records = []
    for head in buckets:
        curr = head
        while curr:
            all_records.append((curr['n'], curr['p']))
            curr = curr['nxt']
    all_records.sort(key=lambda x: x[0])
    return all_records








# Узел дерева: {'n': имя, 'p': телефон, 'l': левый, 'r': правый}
def bst_create_node(name, phone):
    return {'n': name, 'p': phone, 'l': None, 'r': None}

def bst_insert(root, name, phone):
    if root is None:
        return bst_create_node(name, phone)
    # итеративная вставка (без рекурсии)
    parent = None
    cur = root
    while cur:
        parent = cur
        if name == cur['n']:
            cur['p'] = phone
            return root
        elif name < cur['n']:
            cur = cur['l']
        else:
            cur = cur['r']
    # вставляем как лист
    if name < parent['n']:
        parent['l'] = bst_create_node(name, phone)
    else:
        parent['r'] = bst_create_node(name, phone)
    return root

def bst_find(root, name):
    cur = root
    while cur:
        if name == cur['n']:
            return cur['p']
        elif name < cur['n']:
            cur = cur['l']
        else:
            cur = cur['r']
    return None

def _bst_min(node):
    while node['l']:
        node = node['l']
    return node

def bst_delete(root, name):
    if root is None:
        return None
    # поиск узла и родителя
    parent = None
    cur = root
    while cur and cur['n'] != name:
        parent = cur
        if name < cur['n']:
            cur = cur['l']
        else:
            cur = cur['r']
    if cur is None:
        return root
    # случай 0 или 1 ребёнок
    if cur['l'] is None or cur['r'] is None:
        child = cur['l'] if cur['l'] else cur['r']
        if parent is None:
            return child
        if parent['l'] == cur:
            parent['l'] = child
        else:
            parent['r'] = child
    else:
        # два ребёнка - ищем inorder-преемника
        succ_parent = cur
        succ = cur['r']
        while succ['l']:
            succ_parent = succ
            succ = succ['l']
        cur['n'], cur['p'] = succ['n'], succ['p']
        if succ_parent['l'] == succ:
            succ_parent['l'] = succ['r']
        else:
            succ_parent['r'] = succ['r']
    return root

def bst_list_all(root):
    result = []
    def inorder(node):
        if node:
            inorder(node['l'])
            result.append((node['n'], node['p']))
            inorder(node['r'])
    inorder(root)
    return result






# TESTING

if __name__ == '__main__':
    print("=== Linked list test ===")
    head = None
    head = ll_insert(head, "Ivan", "111")
    head = ll_insert(head, "Anna", "222")
    head = ll_insert(head, "Ivan", "333")
    print(ll_find(head, "Ivan"))   # 333
    print(ll_list_all(head))       # [('Anna','222'),('Ivan','333')]
    head = ll_delete(head, "Anna")
    print(ll_list_all(head))       # [('Ivan','333')]

    print("\n=== Hash table test ===")
    buckets = ht_create()
    ht_insert(buckets, "Ivan", "111")
    ht_insert(buckets, "Boris", "444")
    print(ht_find(buckets, "Ivan"))   # 111
    print(ht_list_all(buckets))       # [('Boris','444'),('Ivan','111')]

    print("\n=== BST test ===")
    root = None
    root = bst_insert(root, "Ivan", "111")
    root = bst_insert(root, "Anna", "222")
    root = bst_insert(root, "Ivan", "333")
    print(bst_find(root, "Ivan"))     # 333
    print(bst_list_all(root))         # [('Anna','222'),('Ivan','333')]