# 1. СВЯЗНЫЙ СПИСОК
def ll_create_node(name, phone):
    return {'name': name, 'phone': phone, 'next': None}


def ll_insert(head, name, phone):
    """Добавить или обновить запись. Возвращает голову списка."""
    node = head
    while node is not None:
        if node['name'] == name:
            node['phone'] = phone   # обновить
            return head
        node = node['next']
    # Вставка в начало — O(1)
    new_node = ll_create_node(name, phone)
    new_node['next'] = head
    return new_node


def ll_find(head, name):
    """Вернуть телефон или 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, node = head, head['next']
    while node is not None:
        if node['name'] == name:
            prev['next'] = node['next']
            return head
        prev, node = node, node['next']
    return head


def ll_list_all(head):
    """Собрать все записи и вернуть отсортированный список (name, phone)."""
    result = []
    node = head
    while node is not None:
        result.append((node['name'], node['phone']))
        node = node['next']
    result.sort(key=lambda x: x[0])
    return result

# 2. ХЕШ-ТАБЛИЦА (цепочки через связный список)

HT_SIZE = 1024   # число корзин (степень двойки)


def ht_create(size=HT_SIZE):
    return [None] * size


def _ht_hash(name, size):
    h = 5381
    for ch in name:
        h = ((h << 5) + h) ^ ord(ch)
    return h % size


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


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


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


def ht_list_all(buckets):
    result = []
    for head in buckets:
        result.extend(ll_list_all(head))
    result.sort(key=lambda x: x[0])
    return result

# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST)

def bst_create_node(name, phone):
    return {'name': name, 'phone': phone, 'left': None, 'right': None}


def bst_insert(root, name, phone):
    """Вставить / обновить. Возвращает корень."""
    if root is None:
        return bst_create_node(name, phone)
    if name == root['name']:
        root['phone'] = phone
    elif name < root['name']:
        root['left'] = bst_insert(root['left'], name, phone)
    else:
        root['right'] = bst_insert(root['right'], name, phone)
    return root


def bst_find(root, name):
    """Вернуть телефон или None."""
    while root is not None:
        if name == root['name']:
            return root['phone']
        elif name < root['name']:
            root = root['left']
        else:
            root = root['right']
    return None


def _bst_min(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(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) обход → отсортированный список."""
    result = []
    stack = []
    node = root
    while stack or node is not None:
        while node is not None:
            stack.append(node)
            node = node['left']
        node = stack.pop()
        result.append((node['name'], node['phone']))
        node = node['right']
    return result

"""
Экспериментальная часть: замер производительности трёх структур данных.
"""

import time
import csv
import random
import os
import sys

sys.setrecursionlimit(30000)   # BST с отсортированными данными — глубокая рекурсия


# ── Параметры ──────────────────────────────────────────────────────────────
N = 10_000        # размер набора
REPEATS = 5       # повторений каждого замера
SEARCH_N = 100    # запросов на поиск (существующих)
SEARCH_MISS = 10  # запросов на поиск (отсутствующих)
DELETE_N = 50     # удалений

random.seed(42)

# ── Генерация данных ───────────────────────────────────────────────────────
records_sorted = [(f"User_{i:05d}", f"+7-000-{i:07d}") for i in range(N)]
records_shuffled = records_sorted[:]
random.shuffle(records_shuffled)

search_names_hit  = [records_sorted[i][0] for i in random.sample(range(N), SEARCH_N)]
search_names_miss = [f"None_{i:04d}" for i in range(SEARCH_MISS)]
search_names = search_names_hit + search_names_miss

delete_names = [records_sorted[i][0] for i in random.sample(range(N), DELETE_N)]

# ── Вспомогательные функции ────────────────────────────────────────────────

def build_ll(records):
    head = None
    for name, phone in records:
        head = ll_insert(head, name, phone)
    return head

def build_ht(records):
    buckets = ht_create()
    for name, phone in records:
        ht_insert(buckets, name, phone)
    return buckets

def build_bst(records):
    root = None
    for name, phone in records:
        root = bst_insert(root, name, phone)
    return root

STRUCTURES = {
    'LinkedList': {
        'build':  build_ll,
        'find':   ll_find,
        'delete': lambda ds, name: ll_delete(ds, name),   # возвращает новый head
        'list_all': ll_list_all,
        'mutable': False,  # ll_delete возвращает новую голову
    },
    'HashTable': {
        'build':  build_ht,
        'find':   ht_find,
        'delete': lambda ds, name: ht_delete(ds, name),   # in-place, returns None
        'list_all': ht_list_all,
        'mutable': True,
    },
    'BST': {
        'build':  build_bst,
        'find':   bst_find,
        'delete': lambda ds, name: bst_delete(ds, name),  # возвращает новый корень
        'list_all': bst_list_all,
        'mutable': False,
    },
}

MODES = {
    'shuffled': records_shuffled,
    'sorted':   records_sorted,
}

# ── Замер ──────────────────────────────────────────────────────────────────

def measure(fn, *args, repeats=REPEATS):
    times = []
    for _ in range(repeats):
        t0 = time.perf_counter()
        fn(*args)
        times.append(time.perf_counter() - t0)
    return times

rows = [["structure", "mode", "operation", "run", "time_sec"]]

for struct_name, ops in STRUCTURES.items():
    for mode_name, records in MODES.items():
        print(f"  {struct_name} / {mode_name} ...", flush=True)

        # ── А. Вставка ──────────────────────────────────────────────────
        insert_times = []
        for run in range(REPEATS):
            t0 = time.perf_counter()
            ds = ops['build'](records)
            insert_times.append(time.perf_counter() - t0)
            rows.append([struct_name, mode_name, "insert", run + 1, insert_times[-1]])

        # Строим структуру один раз для поиска и удаления
        ds = ops['build'](records)

        # ── Б. Поиск ────────────────────────────────────────────────────
        def do_search(ds=ds):
            for name in search_names:
                ops['find'](ds, name)

        search_times = measure(do_search)
        for run, t in enumerate(search_times, 1):
            rows.append([struct_name, mode_name, "find", run, t])

        # ── В. Удаление ─────────────────────────────────────────────────
        # Удаление изменяет структуру, поэтому каждый раз пересобираем
        delete_times = []
        for run in range(REPEATS):
            ds2 = ops['build'](records)
            t0 = time.perf_counter()
            for name in delete_names:
                result = ops['delete'](ds2, name)
                if result is not None:   # ll / bst возвращают новую голову/корень
                    ds2 = result
            delete_times.append(time.perf_counter() - t0)
            rows.append([struct_name, mode_name, "delete", run + 1, delete_times[-1]])

        print(f"    insert avg={sum(insert_times)/REPEATS:.4f}s  "
              f"find avg={sum(search_times)/REPEATS:.4f}s  "
              f"delete avg={sum(delete_times)/REPEATS:.4f}s")


with open("results.csv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f).writerows(rows)

print("\nРезультаты сохранены в docs/data/results.csv")