forked from UNN/2026-rff_mp
292 lines
10 KiB
Python
292 lines
10 KiB
Python
import csv
|
||
import random
|
||
import sys
|
||
import time
|
||
import threading
|
||
import matplotlib.pyplot as plt
|
||
|
||
|
||
sys.setrecursionlimit(30000)
|
||
threading.stack_size(64*1024*1024)
|
||
|
||
|
||
def ll_insert(head, name, phone):
|
||
current = head
|
||
while current is not None:
|
||
if current["name"] == name:
|
||
current["phone"] = phone
|
||
return head
|
||
current = current["next"]
|
||
|
||
fresh_node = {"name": name, "phone": phone, "next": head}
|
||
return fresh_node
|
||
|
||
def ll_find(head, name):
|
||
current = head
|
||
while current is not None:
|
||
if current["name"] == name:
|
||
return current["phone"]
|
||
current = current["next"]
|
||
return None
|
||
|
||
def ll_delete(head, name):
|
||
current = head
|
||
previous = None
|
||
|
||
while current is not None:
|
||
if current["name"] == name:
|
||
if previous is None:
|
||
return current["next"]
|
||
else:
|
||
previous["next"] = current["next"]
|
||
return head
|
||
previous = current
|
||
current = current["next"]
|
||
|
||
return head
|
||
|
||
def ll_list_all(head):
|
||
entries = []
|
||
current = head
|
||
while current is not None:
|
||
entries.append((current["name"], current["phone"]))
|
||
current = current["next"]
|
||
entries.sort(key=lambda item: item[0])
|
||
return entries
|
||
|
||
|
||
def ht_create(size=1000):
|
||
return [None] * size
|
||
|
||
def ht_insert(buckets, name, phone):
|
||
bucket_idx = abs(hash(name)) % len(buckets)
|
||
buckets[bucket_idx] = ll_insert(buckets[bucket_idx], name, phone)
|
||
|
||
def ht_find(buckets, name):
|
||
bucket_idx = abs(hash(name)) % len(buckets)
|
||
return ll_find(buckets[bucket_idx], name)
|
||
|
||
def ht_delete(buckets, name):
|
||
bucket_idx = abs(hash(name)) % len(buckets)
|
||
buckets[bucket_idx] = ll_delete(buckets[bucket_idx], name)
|
||
|
||
def ht_list_all(buckets):
|
||
entries = []
|
||
for head_node in buckets:
|
||
current = head_node
|
||
while current is not None:
|
||
entries.append((current["name"], current["phone"]))
|
||
current = current["next"]
|
||
entries.sort(key=lambda item: item[0])
|
||
return entries
|
||
|
||
|
||
def bst_insert(root, name, phone):
|
||
if root is None:
|
||
return {"name": name, "phone": phone, "left": None, "right": None}
|
||
|
||
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):
|
||
if root is None:
|
||
return None
|
||
|
||
if name == root["name"]:
|
||
return root["phone"]
|
||
elif name < root["name"]:
|
||
return bst_find(root["left"], name)
|
||
else:
|
||
return bst_find(root["right"], name)
|
||
|
||
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 = root["right"]
|
||
while successor["left"] is not None:
|
||
successor = successor["left"]
|
||
|
||
root["name"] = successor["name"]
|
||
root["phone"] = successor["phone"]
|
||
root["right"] = bst_delete(root["right"], successor["name"])
|
||
|
||
return root
|
||
|
||
def bst_list_all(root):
|
||
entries = []
|
||
def _inorder(node):
|
||
if node is not None:
|
||
_inorder(node["left"])
|
||
entries.append((node["name"], node["phone"]))
|
||
_inorder(node["right"])
|
||
|
||
_inorder(root)
|
||
return entries
|
||
|
||
|
||
def perform_benchmark():
|
||
total_records = 10000
|
||
random.seed(42)
|
||
|
||
ordered_records = [(f"User_{i:05d}", f"8-999-123-{i:04d}") for i in range(total_records)]
|
||
shuffled_records = ordered_records.copy()
|
||
random.shuffle(shuffled_records)
|
||
|
||
existing_searches = [random.choice(ordered_records)[0] for _ in range(100)]
|
||
non_existing_searches = [f"None_{i}" for i in range(10)]
|
||
search_queries = existing_searches + non_existing_searches
|
||
deletion_targets = [random.choice(ordered_records)[0] for _ in range(50)]
|
||
|
||
output_rows = [["Structure", "Mode", "Operation", "Time (sec)"]]
|
||
graph_entries = []
|
||
|
||
def execute_trial(structure_kind, data_mode, data_collection):
|
||
print(f"Starting: {structure_kind} | Mode: {data_mode}...")
|
||
insertion_measurements, search_measurements, deletion_measurements = [], [], []
|
||
|
||
for trial_num in range(1, 6):
|
||
if structure_kind == "LinkedList": container = None
|
||
elif structure_kind == "HashTable": container = ht_create(size=1000)
|
||
elif structure_kind == "BST": container = None
|
||
|
||
#Вставка
|
||
timer_start = time.perf_counter()
|
||
if structure_kind == "LinkedList":
|
||
for name, phone in data_collection: container = ll_insert(container, name, phone)
|
||
elif structure_kind == "HashTable":
|
||
for name, phone in data_collection: ht_insert(container, name, phone)
|
||
elif structure_kind == "BST":
|
||
for name, phone in data_collection: container = bst_insert(container, name, phone)
|
||
insert_elapsed = time.perf_counter() - timer_start
|
||
insertion_measurements.append(insert_elapsed)
|
||
output_rows.append([structure_kind, data_mode, f"insert (trial {trial_num})", f"{insert_elapsed:.6f}"])
|
||
|
||
#Поиск
|
||
timer_start = time.perf_counter()
|
||
if structure_kind == "LinkedList":
|
||
for name in search_queries: ll_find(container, name)
|
||
elif structure_kind == "HashTable":
|
||
for name in search_queries: ht_find(container, name)
|
||
elif structure_kind == "BST":
|
||
for name in search_queries: bst_find(container, name)
|
||
search_elapsed = time.perf_counter() - timer_start
|
||
search_measurements.append(search_elapsed)
|
||
output_rows.append([structure_kind, data_mode, f"find (trial {trial_num})", f"{search_elapsed:.6f}"])
|
||
|
||
#Удаление
|
||
timer_start = time.perf_counter()
|
||
if structure_kind == "LinkedList":
|
||
for name in deletion_targets: container = ll_delete(container, name)
|
||
elif structure_kind == "HashTable":
|
||
for name in deletion_targets: ht_delete(container, name)
|
||
elif structure_kind == "BST":
|
||
for name in deletion_targets: container = bst_delete(container, name)
|
||
delete_elapsed = time.perf_counter() - timer_start
|
||
deletion_measurements.append(delete_elapsed)
|
||
output_rows.append([structure_kind, data_mode, f"delete (trial {trial_num})", f"{delete_elapsed:.6f}"])
|
||
|
||
# Запись средних значений
|
||
output_rows.append([structure_kind, data_mode, "Insert (avg)", f"{sum(insertion_measurements)/5:.6f}"])
|
||
output_rows.append([structure_kind, data_mode, "Find (avg)", f"{sum(search_measurements)/5:.6f}"])
|
||
output_rows.append([structure_kind, data_mode, "Delete (avg)", f"{sum(deletion_measurements)/5:.6f}"])
|
||
|
||
avg_insertion = sum(insertion_measurements) / 5
|
||
avg_search = sum(search_measurements) / 5
|
||
avg_deletion = sum(deletion_measurements) / 5
|
||
|
||
graph_entries.append((structure_kind, data_mode, avg_insertion, avg_search, avg_deletion))
|
||
|
||
# Запуск всех тестов
|
||
execute_trial("LinkedList", "random", shuffled_records)
|
||
execute_trial("LinkedList", "sorted", ordered_records)
|
||
execute_trial("HashTable", "random", shuffled_records)
|
||
execute_trial("HashTable", "sorted", ordered_records)
|
||
execute_trial("BST", "random", shuffled_records)
|
||
execute_trial("BST", "sorted", ordered_records)
|
||
|
||
with open("benchmark_output.csv", "w", newline="", encoding="utf-8") as csv_file:
|
||
csv_writer = csv.writer(csv_file)
|
||
csv_writer.writerows(output_rows)
|
||
print("\n[Success] All benchmarks completed! Results saved to 'benchmark_output.csv'.")
|
||
|
||
|
||
|
||
generate_performance_charts(graph_entries)
|
||
|
||
def generate_performance_charts(plot_data):
|
||
|
||
if not plot_data:
|
||
print("Нет данных для построения графиков")
|
||
return
|
||
|
||
# Подготовка данных
|
||
structures = ['LinkedList', 'HashTable', 'BST']
|
||
modes = ['random', 'sorted']
|
||
operations = ['Insert', 'Find', 'Delete']
|
||
|
||
# Создаем фигуру с тремя подграфиками
|
||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||
|
||
for idx, operation in enumerate(operations):
|
||
ax = axes[idx]
|
||
|
||
# Данные для текущей операции
|
||
x_positions = []
|
||
y_values = []
|
||
labels = []
|
||
|
||
for structure in structures:
|
||
for mode in modes:
|
||
# Находим данные для этой комбинации
|
||
for data in plot_data:
|
||
if data[0] == structure and data[1] == mode:
|
||
value = data[2 + idx] # 2=insert, 3=find, 4=delete
|
||
x_positions.append(len(x_positions))
|
||
y_values.append(value)
|
||
labels.append(f"{structure}\n{mode}")
|
||
|
||
# Создаем столбчатую диаграмму
|
||
bars = ax.bar(x_positions, y_values, color=['skyblue', 'lightcoral']*3)
|
||
|
||
# Настройка графика
|
||
ax.set_title(f'Операция: {operation}', fontsize=14, fontweight='bold')
|
||
ax.set_ylabel('Время (сек)', fontsize=12)
|
||
ax.set_xticks(x_positions)
|
||
ax.set_xticklabels(labels, rotation=45, ha='right', fontsize=10)
|
||
ax.grid(axis='y', alpha=0.3)
|
||
|
||
# Добавляем значения над столбцами
|
||
for bar, value in zip(bars, y_values):
|
||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
|
||
f'{value:.4f}', ha='center', va='bottom', fontsize=8)
|
||
|
||
plt.tight_layout()
|
||
plt.savefig('performance_charts.png', dpi=300, bbox_inches='tight')
|
||
plt.show()
|
||
print("Графики сохранены в 'performance_charts.png'")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
benchmark_thread = threading.Thread(target=perform_benchmark)
|
||
benchmark_thread.start()
|
||
benchmark_thread.join()
|
||
|
||
|
||
print("\nПрограмма завершена. Проверьте файл 'performance_charts.png'") |