import random
import time
import csv

# ---------- Реализация связного списка ----------
def ll_insert_begin(head, name, phone):
# Вставка узла в начало списка. Возвращает новую голову.
    new_node = {'name': name, 'phone': phone, 'next': head}
    return new_node

def ll_find(head, name):
# Поиск телефона по имени. Возвращает phone или None.
    current = head
    while current:
        if current['name'] == name:
            return current['phone']
        current = current['next']
    return None

def ll_delete(head, name):
# Удаление узла по имени. Возвращает новую голову.
    if head is None:
        return None
    if head['name'] == name:
        return head['next']
    current = head
    while current['next']:
        if current['next']['name'] == name:
            current['next'] = current['next']['next']
            return head
        current = current['next']
    return head

def ll_list_all(head):
# Собирает все записи в список и сортирует по имени.
    records = []
    current = head
    while current:
        records.append((current['name'], current['phone']))
        current = current['next']
    records.sort(key=lambda x: x[0])
    return records



# ---------- Измерения для массива ----------
def array_insert_measure(records, sorted_flag=False):
# Вставка записей в начало массива. Возвращает время.
    arr = []
    start = time.perf_counter()
    if sorted_flag:
        # records уже отсортированы
        for item in records:
            arr.insert(0, item)
    else:
        for item in records:
            arr.insert(0, item)
    end = time.perf_counter()
    return end - start

def array_find_measure(records, test_names):
# Поиск в массиве: линейный перебор.
    start = time.perf_counter()
    for name in test_names:
        for rec in records:
            if rec[0] == name:
                break
    end = time.perf_counter()
    return end - start

def array_delete_measure(records, delete_names):
# Удаление из массива через создание нового списка (как в оригинале).
    times = []
    for name in delete_names:
        start = time.perf_counter()
        records = [rec for rec in records if rec[0] != name]
        end = time.perf_counter()
        times.append(end - start)
    return sum(times) / len(times) if times else 0



# ---------- Измерения для связного списка ----------
def linked_insert_measure(records, sorted_flag=False):
# Вставка записей в начало связного списка. Возвращает время.
    head = None
    start = time.perf_counter()
    # Если sorted_flag == True, records уже отсортированы, но для связного списка
    # вставка в начало всегда O(1), порядок не влияет на время.
    for name, phone in records:
        head = ll_insert_begin(head, name, phone)
    end = time.perf_counter()
    return end - start

def linked_find_measure(head, test_names):
# Поиск в связном списке.
    start = time.perf_counter()
    for name in test_names:
        ll_find(head, name)
    end = time.perf_counter()
    return end - start

def linked_delete_measure(head, delete_names):
# Удаление из связного списка.
    times = []
    for name in delete_names:
        start = time.perf_counter()
        head = ll_delete(head, name)
        end = time.perf_counter()
        times.append(end - start)
    return sum(times) / len(times) if times else 0



# ---------- Основная функция эксперимента ----------
def main():
    N = 10000
    # Генерация тестовых данных
    records = []
    for i in range(N):
        name = f"User_{i:05d}"
        phone = f"8{random.randint(9000000000, 9999999999)}"
        records.append((name, phone))

    records_shuffled = records.copy()
    random.shuffle(records_shuffled)
    records_sorted = sorted(records, key=lambda x: x[0])

    # Имена для поиска (100 существующих + 10 несуществующих)
    existing_names = random.sample([rec[0] for rec in records], 100)
    non_existing = [f"None_{i}" for i in range(10)]
    test_names = existing_names + non_existing

    # Имена для удаления (50 случайных)
    delete_names = random.sample([rec[0] for rec in records], 50)

    # Результаты будем собирать в список списков
    results = [["Структура", "Режим", "Операция", "Время (сек)"]]



    # ----- Массив -----
    # Вставка (случайный порядок)
    arr_time_shuffled = 0.0
    arr_time_sorted = 0.0
    for _ in range(5):
        arr_time_shuffled += array_insert_measure(records_shuffled, sorted_flag=False)
        arr_time_sorted += array_insert_measure(records_sorted, sorted_flag=True)
    results.append(["Array", "случайный", "вставка (в начало)", arr_time_shuffled / 5])
    results.append(["Array", "отсортированный", "вставка (в начало)", arr_time_sorted / 5])

    # Поиск
    find_time = 0.0
    for _ in range(5):
        find_time += array_find_measure(records, test_names)
    results.append(["Array", "любой", "поиск 110 записей", find_time / 5])

    # Удаление
    del_time = 0.0
    for _ in range(5):
        del_time += array_delete_measure(records.copy(), delete_names)
    results.append(["Array", "любой", "удаление 50 записей (среднее)", del_time / 5])



    # ----- Связный список -----
    # Вставка
    ll_time_shuffled = 0.0
    ll_time_sorted = 0.0
    for _ in range(5):
        ll_time_shuffled += linked_insert_measure(records_shuffled)
        ll_time_sorted += linked_insert_measure(records_sorted)
    results.append(["Linked list", "случайный", "вставка (в начало)", ll_time_shuffled / 5])
    results.append(["Linked list", "отсортированный", "вставка (в начало)", ll_time_sorted / 5])

    # Поиск (предварительно строим список)
    head = None
    for name, phone in records:
        head = ll_insert_begin(head, name, phone)
    find_time_ll = 0.0
    for _ in range(5):
        find_time_ll += linked_find_measure(head, test_names)
    results.append(["Linked list", "любой", "поиск 110 записей", find_time_ll / 5])

    # Удаление (копируем список для каждого замера)
    del_time_ll = 0.0
    for _ in range(5):
        # Строим новую копию списка
        h = None
        for name, phone in records:
            h = ll_insert_begin(h, name, phone)
        del_time_ll += linked_delete_measure(h, delete_names)
    results.append(["Linked list", "любой", "удаление 50 записей (среднее)", del_time_ll / 5])

    # ----- Вывод результатов в единый столбец -----
    print("\nРезультаты экспериментов (время в секундах):\n")
    # Определяем максимальную ширину первого столбца для красивого выравнивания
    col_widths = [max(len(str(row[i])) for row in results) for i in range(4)]
    for row in results:
        print(f"{row[0]:<{col_widths[0]}}  {row[1]:<{col_widths[1]}}  "
              f"{row[2]:<{col_widths[2]}}  {row[3]:<{col_widths[3]}}")

# ----- Запись результатов в CSV-файл -----
    with open('results.csv', 'w', newline='', encoding='utf-8-sig') as csvfile:
        writer = csv.writer(csvfile, delimiter = ';')
        writer.writerows(results)

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


if __name__ == "__main__":
    main()