1
0
forked from UNN/2026-rff_mp
2026-rff_mp/zhigalovrd/lab1/src/experiment.py
2026-05-23 20:54:44 +03:00

387 lines
15 KiB
Python

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 создан")