# Task 1
import time
import random
import csv
from collections import defaultdict

def ll_insert(head, name, phone):
    new = {'name': name, 'phone': phone, 'next': None}
    if head is None:
        return new
    cur = head
    while cur:
        if cur['name'] == name:
            cur['phone'] = phone
            return head
        if cur['next'] is None:
            break
        cur = cur['next']
    cur['next'] = new
    return head

def ll_find(head, name):
    cur = head
    while cur:
        if cur['name'] == name:
            return cur['phone']
        cur = cur['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:
        if cur['name'] == name:
            prev['next'] = cur['next']
            return head
        prev = cur
        cur = cur['next']
    return head

def ll_list_all(head):
    rec = []
    cur = head
    while cur:
        rec.append((cur['name'], cur['phone']))
        cur = cur['next']
    rec.sort(key=lambda x: x[0])
    return rec

# 2. Хеш-таблица
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):
    rec = []
    for b in buckets:
        cur = b
        while cur:
            rec.append((cur['name'], cur['phone']))
            cur = cur['next']
    rec.sort(key=lambda x: x[0])
    return rec

def bst_insert(root, name, phone):
    new = {'name': name, 'phone': phone, 'left': None, 'right': None}
    if root is None:
        return new
    cur = root
    while True:
        if name < cur['name']:
            if cur['left'] is None:
                cur['left'] = new
                break
            cur = cur['left']
        elif name > cur['name']:
            if cur['right'] is None:
                cur['right'] = new
                break
            cur = cur['right']
        else:
            cur['phone'] = phone
            break
    return root

def bst_find(root, name):
    cur = root
    while cur:
        if name == cur['name']:
            return cur['phone']
        elif name < cur['name']:
            cur = cur['left']
        else:
            cur = cur['right']
    return None

def bst_delete(root, name):
    if root is None:
        return None
    parent = None
    cur = root
    while cur and cur['name'] != name:
        parent = cur
        if name < cur['name']:
            cur = cur['left']
        else:
            cur = cur['right']
    if cur is None:
        return root
    # нет детей
    if cur['left'] is None and cur['right'] is None:
        if parent is None:
            return None
        if parent['left'] is cur:
            parent['left'] = None
        else:
            parent['right'] = None
        return root
    # один ребёнок
    if cur['left'] is None:
        child = cur['right']
    elif cur['right'] is None:
        child = cur['left']
    else:
        # два ребёнка ищем минимальный в правом поддереве
        succ_parent = cur
        succ = cur['right']
        while succ['left']:
            succ_parent = succ
            succ = succ['left']
        cur['name'], cur['phone'] = succ['name'], succ['phone']
        if succ_parent['left'] is succ:
            succ_parent['left'] = succ['right']
        else:
            succ_parent['right'] = succ['right']
        return root
    if parent is None:
        return child
    if parent['left'] is cur:
        parent['left'] = child
    else:
        parent['right'] = child
    return root

def bst_list_all(root):
    res = []
    stack = []
    cur = root
    while stack or cur:
        while cur:
            stack.append(cur)
            cur = cur['left']
        cur = stack.pop()
        res.append((cur['name'], cur['phone']))
        cur = cur['right']
    return res

def gen_data(N=10000):
    data = []
    for i in range(N):
        name = f"user_{i:06d}"
        phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}"
        data.append((name, phone))
    shuffled = data[:]
    random.shuffle(shuffled)
    sorted_data = sorted(data, key=lambda x: x[0])
    return shuffled, sorted_data

# 5 повторений сохраняем все замеры
def run_test(init_func, ins_func, find_func, del_func, list_func,
             shuffled, sorted_data, exist_names, missing_names, del_names):
    rows = []
    for order, dataset in [('shuffled', shuffled), ('sorted', sorted_data)]:
        for rep in range(5):
            s = init_func()
            # вставка
            t1 = time.perf_counter()
            for name, phone in dataset:
                s = ins_func(s, name, phone)
            t2 = time.perf_counter()
            rows.append([order, rep+1, 'insert', t2 - t1])
            # поиск
            t1 = time.perf_counter()
            for name in exist_names:
                find_func(s, name)
            for name in missing_names:
                find_func(s, name)
            t2 = time.perf_counter()
            rows.append([order, rep+1, 'find', t2 - t1])
            # удаление
            t1 = time.perf_counter()
            for name in del_names:
                s = del_func(s, name)
            t2 = time.perf_counter()
            rows.append([order, rep+1, 'delete', t2 - t1])

            t1 = time.perf_counter()
            list_func(s)
            t2 = time.perf_counter()
            rows.append([order, rep+1, 'list_all', t2 - t1])
    return rows

if __name__ == '__main__':
    N = 10000
    print(f"Генерация {N} записей...")
    shuffled, sorted_data = gen_data(N)

    exist_names = [name for name, _ in shuffled[:100]]
    missing_names = [f"none_{i}" for i in range(10)]
    all_names = [name for name, _ in shuffled]
    del_names = random.sample(all_names, 50)

    all_rows = []  # для CSV

    print("Связный список...")
    rows = run_test(lambda: None, ll_insert, ll_find, ll_delete, ll_list_all,
                    shuffled, sorted_data, exist_names, missing_names, del_names)
    for r in rows:
        all_rows.append(['LinkedList'] + r)

    print("Хеш-таблица...")
    rows = run_test(lambda: [None]*2000, ht_insert, ht_find, ht_delete, ht_list_all,
                    shuffled, sorted_data, exist_names, missing_names, del_names)
    for r in rows:
        all_rows.append(['HashTable'] + r)

    # бинарное дерево
    print("Бинарное дерево...")
    rows = run_test(lambda: None, bst_insert, bst_find, bst_delete, bst_list_all,
                    shuffled, sorted_data, exist_names, missing_names, del_names)
    for r in rows:
        all_rows.append(['BST'] + r)

    # запись в CSV
    with open('results.csv', 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(['Структура', 'Порядок', 'Повторение', 'Операция', 'Время (с)'])
        writer.writerows(all_rows)

    print("Все замеры сохранены в results.csv")

    # Подсчёт и вывод средних
    avg = defaultdict(list)
    for row in all_rows:
        struct, order, rep, op, t = row
        avg[(struct, order, op)].append(t)
    print("\nСредние значения (5 запусков):")
    print(f"{'Структура':<12} {'Порядок':<10} {'Вставка':>10} {'Поиск':>10} {'Удаление':>10} {'ListAll':>10}")
    for struct in ['LinkedList', 'HashTable', 'BST']:
        for order in ['shuffled', 'sorted']:
            row = [struct, order]
            for op in ['insert', 'find', 'delete', 'list_all']:
                vals = avg[(struct, order, op)]
                avg_val = sum(vals)/len(vals)
                row.append(f"{avg_val:.5f}")
            print(f"{row[0]:<12} {row[1]:<10} {row[2]:>10} {row[3]:>10} {row[4]:>10} {row[5]:>10}")
    print("\nГотово!!!!!.")