#--------------------------------------Связный список--------------------------
def ll_insert(head, name, phone):
    # 1. если список пуст → новый элемент становится head
    if head is None:
        return {'name': name, 'phone': phone,'next': None}

    current = head
    # 2. сначала проверим — может имя уже есть → тогда просто обновим
    while current is not None:
        if current['name'] == name:
            current['phone'] = phone
            return head
        current = current['next']
    # 3. идём до конца списка
    current = head
    while current['next'] is not None:
        current = current['next']
    # 4. добавляем новый узел в конец
    current['next'] = {'name': name,'phone': phone,'next': None}

    return head


def ll_find(head, name):
    current = head

    while current is not None:
        if current['name'] == name:
            return current['phone']

        current = current['next']

    return None

def ll_delete(head, name):
    current = head
    previous = None

    while current is not None:
        if current['name'] == name:

            # 1. удаляем голову списка
            if previous is None:
                return current['next']

            # 2. удаляем середину или конец
            previous['next'] = current['next']
            return head

        previous = current
        current = current['next']

    return head  # если не нашли

def ll_list_all(head):
    result = []
    current = head
    # 1. проходим по списку
    while current is not None:
        result.append((current['name'], current['phone']))
        current = current['next']
    # 2. сортируем по имени
    result.sort(key=lambda x: x[0])

    return result


#----------------------------------HASH-таблица--------------------------------
def my_hash(s, M):
    B = 31
    n = len(s)
    h = 0
    for i in range(n):
        h += ord(s[i]) * (B ** (n - 1 - i))
    return h % M

def ht_insert(buckets, name, phone):
    index = my_hash(name, len(buckets))
    buckets[index] = ll_insert(buckets[index], name, phone)
    return buckets


def ht_find(buckets, name):
    index = my_hash(name, len(buckets))
    return ll_find(buckets[index], name)
    
def ht_delete(buckets, name):
    index = my_hash(name, len(buckets))
    buckets[index] = ll_delete(buckets[index], name)
    return buckets

def ht_list_all(buckets):
    result = []
    for i in range(len(buckets)):
        result += ll_list_all(buckets[i])
    result.sort(key=lambda x: x[0])
    return result
    
#---------------------------Двоичное дерево поиска-----------------------------
def bst_insert(root, name, phone):
    if root is None:
        return {'name': name, 'phone': phone,'left': None, 'right': None}
    
    current = root
    while True:
        # если такое имя уже есть — меняем телефон
        if name == current['name']:
            current['phone'] = phone
            return root

        # если новое имя меньше — идём влево
        if name < current['name']:
            if current['left'] is None:
                current['left'] = {'name': name, 'phone': phone,'left': None, 'right': None}
                return root
            current = current['left']

        # если новое имя больше — идём вправо
        else:
            if current['right'] is None:
                current['right'] = {'name': name, 'phone': phone,'left': None, 'right': None}
                return root
            current = current['right']
            
def bst_find(root, name):
    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 bst_delete(root, name):
    current = root
    previous = None
    
    while current is not None and current['name'] != name:
        previous = current

        if name < current['name']:
            current = current['left']
        else:
            current = current['right']

    # если не нашли
    if current is None:
        return root
    
    # 2. Если у узла два потомка
    if current['left'] is not None and current['right'] is not None:
        successor_parent = current
        successor = current['right']

        # ищем минимальный узел в правом поддереве
        while successor['left'] is not None:
            successor_parent = successor
            successor = successor['left']

        # копируем данные successor в current
        current['name'] = successor['name']
        current['phone'] = successor['phone']

        # теперь удаляем successor
        current = successor
        previous = successor_parent
    #3
    if current['left'] is not None:
        child = current['left']
    else:
        child = current['right']

    # 4. Если удаляем корень
    if previous is None:
        return child

    # 5. Переподключаем родителя
    if previous['left'] is current:
        previous['left'] = child
    else:
        previous['right'] = child

    return root
    
def bst_list_all(root):
    result = []

    def inorder(node):
        if node is None:
            return

        inorder(node['left'])
        result.append((node['name'], node['phone']))
        inorder(node['right'])

    inorder(root)
    return result
    
