
experiment_code = ''

import time
import csv
import random
import sys
import matplotlib.pyplot as plt
import numpy as np

from linked_list import ll_insert, ll_find, ll_delete, ll_list_all
from hash_table import ht_insert, ht_find, ht_delete, ht_list_all
from bst import bst_insert, bst_find, bst_delete, bst_list_all


sys.setrecursionlimit(20000)

# параметры
N = 5000                # Количество записей
NUM_RUNS = 5            # Количество прогонов для усреднения
BUCKET_SIZE = N // 2    # Размер хеш-таблицы (load factor ~2)


def generate_data(n):
    records = []
    for i in range(n):
        name = f"User_{i:05d}"
        phone = f"+7{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])

    return records, records_shuffled, records_sorted


def run_linked_list_experiment(records, search_existing, search_nonexistent, names_to_delete):
    
    head = None

    # Вставка
    start = time.perf_counter()
    for name, phone in records:
        head = ll_insert(head, name, phone)
    end = time.perf_counter()
    insert_time = end - start

    # Поиск
    start = time.perf_counter()
    for name in search_existing:
        ll_find(head, name)
    for name in search_nonexistent:
        ll_find(head, name)
    end = time.perf_counter()
    find_time = end - start

    # Удаление
    start = time.perf_counter()
    for name in names_to_delete:
        head = ll_delete(head, name)
    end = time.perf_counter()
    delete_time = end - start

    return insert_time, find_time, delete_time


def run_hash_table_experiment(records, search_existing, search_nonexistent, names_to_delete):
    
    buckets = [None] * BUCKET_SIZE

    # Вставка
    start = time.perf_counter()
    for name, phone in records:
        ht_insert(buckets, name, phone)
    end = time.perf_counter()
    insert_time = end - start

    # Поиск
    start = time.perf_counter()
    for name in search_existing:
        ht_find(buckets, name)
    for name in search_nonexistent:
        ht_find(buckets, name)
    end = time.perf_counter()
    find_time = end - start

    # Удаление
    start = time.perf_counter()
    for name in names_to_delete:
        ht_delete(buckets, name)
    end = time.perf_counter()
    delete_time = end - start

    return insert_time, find_time, delete_time


def run_bst_experiment(records, search_existing, search_nonexistent, names_to_delete):
    
    root = None

    # Вставка
    start = time.perf_counter()
    for name, phone in records:
        root = bst_insert(root, name, phone)
    end = time.perf_counter()
    insert_time = end - start

    # Поиск
    start = time.perf_counter()
    for name in search_existing:
        bst_find(root, name)
    for name in search_nonexistent:
        bst_find(root, name)
    end = time.perf_counter()
    find_time = end - start

    # Удаление
    start = time.perf_counter()
    for name in names_to_delete:
        root = bst_delete(root, name)
    end = time.perf_counter()
    delete_time = end - start

    return insert_time, find_time, delete_time


def run_all_experiments():
    
    print("=" * 60)
    print("ЭКСПЕРИМЕНТ: Сравнение структур данных")
    print(f"N = {N}, прогонов = {NUM_RUNS}")
    print("=" * 60)

    # Генерация данных
    print("\\n[1/5] Генерация тестовых данных...")
    records, records_shuffled, records_sorted = generate_data(N)

    # Подготовка данных для поиска и удаления (фиксируем seed для воспроизводимости)
    random.seed(42)
    existing_names = [r[0] for r in records]
    search_existing = random.sample(existing_names, 100)
    search_nonexistent = [f"None_{i:05d}" for i in range(10)]
    names_to_delete = random.sample(existing_names, 50)
    print(f"  Записей: {len(records)}")
    print(f"  Поиск: {len(search_existing)} существующих + {len(search_nonexistent)} несуществующих")
    print(f"  Удаление: {len(names_to_delete)} записей")

    # Хранение результатов
    all_results = []

    
    print("\\n[2/5] Linked List...")
    for run in range(NUM_RUNS):
        t_insert, t_find, t_delete = run_linked_list_experiment(
            records_shuffled, search_existing, search_nonexistent, names_to_delete
        )
        all_results.append({
            'Структура': 'LinkedList', 'Режим': 'случайный', 'Прогон': run + 1,
            'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
        })
        print(f"  Случайный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")

    for run in range(NUM_RUNS):
        t_insert, t_find, t_delete = run_linked_list_experiment(
            records_sorted, search_existing, search_nonexistent, names_to_delete
        )
        all_results.append({
            'Структура': 'LinkedList', 'Режим': 'отсортированный', 'Прогон': run + 1,
            'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
        })
        print(f"  Отсортированный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")

    
    print("\\n[3/5] Hash Table...")
    for run in range(NUM_RUNS):
        t_insert, t_find, t_delete = run_hash_table_experiment(
            records_shuffled, search_existing, search_nonexistent, names_to_delete
        )
        all_results.append({
            'Структура': 'HashTable', 'Режим': 'случайный', 'Прогон': run + 1,
            'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
        })
        print(f"  Случайный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")

    for run in range(NUM_RUNS):
        t_insert, t_find, t_delete = run_hash_table_experiment(
            records_sorted, search_existing, search_nonexistent, names_to_delete
        )
        all_results.append({
            'Структура': 'HashTable', 'Режим': 'отсортированный', 'Прогон': run + 1,
            'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
        })
        print(f"  Отсортированный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")

   
    print("\\n[4/5] BST...")
    for run in range(NUM_RUNS):
        t_insert, t_find, t_delete = run_bst_experiment(
            records_shuffled, search_existing, search_nonexistent, names_to_delete
        )
        all_results.append({
            'Структура': 'BST', 'Режим': 'случайный', 'Прогон': run + 1,
            'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
        })
        print(f"  Случайный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")

    for run in range(NUM_RUNS):
        t_insert, t_find, t_delete = run_bst_experiment(
            records_sorted, search_existing, search_nonexistent, names_to_delete
        )
        all_results.append({
            'Структура': 'BST', 'Режим': 'отсортированный', 'Прогон': run + 1,
            'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
        })
        print(f"  Отсортированный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")

  
    print("\\n[5/5] Сохранение результатов...")

    # Сырые данные
    with open('../docs/data/results_raw.csv', 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(['Структура', 'Режим', 'Прогон', 'Вставка (сек)', 'Поиск (сек)', 'Удаление (сек)'])
        for r in all_results:
            writer.writerow([r['Структура'], r['Режим'], r['Прогон'],
                            r['Вставка'], r['Поиск'], r['Удаление']])
    print("  Сохранено: ../docs/data/results_raw.csv")

    # Сводная таблица
    from collections import defaultdict
    avg_results = defaultdict(lambda: {'insert': [], 'find': [], 'delete': []})
    for r in all_results:
        key = (r['Структура'], r['Режим'])
        avg_results[key]['insert'].append(r['Вставка'])
        avg_results[key]['find'].append(r['Поиск'])
        avg_results[key]['delete'].append(r['Удаление'])

    with open('../docs/data/results_summary.csv', 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее (сек)', 'Мин (сек)', 'Макс (сек)'])
        for (struct, mode), times in avg_results.items():
            writer.writerow([struct, mode, 'Вставка',
                            f"{sum(times['insert']) / len(times['insert']):.6f}",
                            f"{min(times['insert']):.6f}",
                            f"{max(times['insert']):.6f}"])
            writer.writerow([struct, mode, 'Поиск',
                            f"{sum(times['find']) / len(times['find']):.6f}",
                            f"{min(times['find']):.6f}",
                            f"{max(times['find']):.6f}"])
            writer.writerow([struct, mode, 'Удаление',
                            f"{sum(times['delete']) / len(times['delete']):.6f}",
                            f"{min(times['delete']):.6f}",
                            f"{max(times['delete']):.6f}"])
    print("  Сохранено: ../docs/data/results_summary.csv")

    print("  Построение графиков...")
    build_charts(avg_results)
    print("  Сохранено: ../docs/data/charts.png")

    print("\\n" + "=" * 60)
    print("ЭКСПЕРИМЕНТ ЗАВЕРШЁН!")
    print("=" * 60)

    return all_results, avg_results


def build_charts(avg_results):
    """Строит графики сравнения производительности."""
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    fig.suptitle(f'Сравнение производительности структур данных (N={N})', fontsize=16, fontweight='bold')

    structures = ['LinkedList', 'HashTable', 'BST']
    modes = ['случайный', 'отсортированный']
    struct_colors = {'LinkedList': '#FF6B6B', 'HashTable': '#4ECDC4', 'BST': '#45B7D1'}

    # Подготовка данных для графиков
    def get_value(struct, mode, op):
        key = (struct, mode)
        if key in avg_results:
            return sum(avg_results[key][op]) / len(avg_results[key][op])
        return 0

    # График 1: Вставка
    ax = axes[0, 0]
    x = np.arange(len(modes))
    width = 0.25
    for i, struct in enumerate(structures):
        vals = [get_value(struct, mode, 'insert') for mode in modes]
        ax.bar(x + i * width, vals, width, label=struct, color=struct_colors[struct])
    ax.set_xlabel('Режим данных')
    ax.set_ylabel('Время (сек)')
    ax.set_title('Вставка')
    ax.set_xticks(x + width)
    ax.set_xticklabels(modes)
    ax.legend()
    ax.set_yscale('log')
    ax.grid(True, alpha=0.3)

    # График 2: Поиск
    ax = axes[0, 1]
    for i, struct in enumerate(structures):
        vals = [get_value(struct, mode, 'find') for mode in modes]
        ax.bar(x + i * width, vals, width, label=struct, color=struct_colors[struct])
    ax.set_xlabel('Режим данных')
    ax.set_ylabel('Время (сек)')
    ax.set_title('Поиск (110 операций)')
    ax.set_xticks(x + width)
    ax.set_xticklabels(modes)
    ax.legend()
    ax.set_yscale('log')
    ax.grid(True, alpha=0.3)

    # График 3: Удаление
    ax = axes[0, 2]
    for i, struct in enumerate(structures):
        vals = [get_value(struct, mode, 'delete') for mode in modes]
        ax.bar(x + i * width, vals, width, label=struct, color=struct_colors[struct])
    ax.set_xlabel('Режим данных')
    ax.set_ylabel('Время (сек)')
    ax.set_title('Удаление (50 операций)')
    ax.set_xticks(x + width)
    ax.set_xticklabels(modes)
    ax.legend()
    ax.set_yscale('log')
    ax.grid(True, alpha=0.3)

    # График 4: BST деградация
    ax = axes[1, 0]
    bst_random = get_value('BST', 'случайный', 'insert')
    bst_sorted = get_value('BST', 'отсортированный', 'insert')
    ax.bar(['Случайный', 'Отсортированный'], [bst_random, bst_sorted],
           color=['#45B7D1', '#E74C3C'])
    ax.set_ylabel('Время (сек)')
    ax.set_title('BST: влияние порядка данных на вставку')
    for i, v in enumerate([bst_random, bst_sorted]):
        ax.text(i, v + max(v * 0.05, 0.01), f'{v:.3f}s', ha='center', fontweight='bold')
    ax.grid(True, alpha=0.3)

    # График 5: Случайный режим — все операции
    ax = axes[1, 1]
    x = np.arange(len(structures))
    width = 0.25
    insert_vals = [get_value(s, 'случайный', 'insert') for s in structures]
    find_vals = [get_value(s, 'случайный', 'find') for s in structures]
    delete_vals = [get_value(s, 'случайный', 'delete') for s in structures]
    ax.bar(x - width, insert_vals, width, label='Вставка', color='#FF6B6B')
    ax.bar(x, find_vals, width, label='Поиск', color='#4ECDC4')
    ax.bar(x + width, delete_vals, width, label='Удаление', color='#45B7D1')
    ax.set_xlabel('Структура данных')
    ax.set_ylabel('Время (сек)')
    ax.set_title('Случайный режим: все операции')
    ax.set_xticks(x)
    ax.set_xticklabels(structures)
    ax.legend()
    ax.set_yscale('log')
    ax.grid(True, alpha=0.3)

    # График 6: Отсортированный режим — поиск и удаление
    ax = axes[1, 2]
    find_vals = [get_value(s, 'отсортированный', 'find') for s in structures]
    delete_vals = [get_value(s, 'отсортированный', 'delete') for s in structures]
    ax.bar(x - width / 2, find_vals, width, label='Поиск', color='#4ECDC4')
    ax.bar(x + width / 2, delete_vals, width, label='Удаление', color='#45B7D1')
    ax.set_xlabel('Структура данных')
    ax.set_ylabel('Время (сек)')
    ax.set_title('Отсортированный режим: поиск и удаление')
    ax.set_xticks(x)
    ax.set_xticklabels(structures)
    ax.legend()
    ax.set_yscale('log')
    ax.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig('../docs/data/charts.png', dpi=150, bbox_inches='tight')
    plt.close()


if __name__ == '__main__':
    run_all_experiments()


with open('/mnt/agents/output/lab1/src/experiment.py', 'w', encoding='utf-8') as f:
    f.write(experiment_code)

print("✅ experiment.py создан")
