Merge pull request '[1] task 1' (#207) from osipovamd/2026-rff_mp:osipovamd into develop
Reviewed-on: #207
This commit is contained in:
commit
f907f973d9
BIN
osipovamd/docs/Task1.docx
Normal file
BIN
osipovamd/docs/Task1.docx
Normal file
Binary file not shown.
19
osipovamd/docs/results.csv
Normal file
19
osipovamd/docs/results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Структура,Режим,Операция,Замер1,Замер2,Замер3,Среднее
|
||||||
|
LinkedList,случайный,вставка,0.017595,0.018368,0.016781,0.017581
|
||||||
|
LinkedList,случайный,поиск,0.002300,0.002235,0.002287,0.002274
|
||||||
|
LinkedList,случайный,удаление,0.000868,0.000819,0.000819,0.000835
|
||||||
|
LinkedList,отсортированный,вставка,0.014638,0.014335,0.014226,0.014400
|
||||||
|
LinkedList,отсортированный,поиск,0.001955,0.001930,0.001897,0.001928
|
||||||
|
LinkedList,отсортированный,удаление,0.000975,0.000984,0.000998,0.000986
|
||||||
|
HashTable,случайный,вставка,0.001593,0.001598,0.001441,0.001544
|
||||||
|
HashTable,случайный,поиск,0.000180,0.000156,0.000154,0.000163
|
||||||
|
HashTable,случайный,удаление,0.000070,0.000068,0.000069,0.000069
|
||||||
|
HashTable,отсортированный,вставка,0.001369,0.001375,0.001383,0.001376
|
||||||
|
HashTable,отсортированный,поиск,0.000168,0.000160,0.000148,0.000159
|
||||||
|
HashTable,отсортированный,удаление,0.000080,0.000077,0.000076,0.000078
|
||||||
|
BST,случайный,вставка,0.000513,0.000501,0.000514,0.000509
|
||||||
|
BST,случайный,поиск,0.000077,0.000059,0.000054,0.000063
|
||||||
|
BST,случайный,удаление,0.000044,0.000042,0.000039,0.000042
|
||||||
|
BST,отсортированный,вставка,0.019008,0.018696,0.018868,0.018857
|
||||||
|
BST,отсортированный,поиск,0.001828,0.001813,0.001811,0.001817
|
||||||
|
BST,отсортированный,удаление,0.001735,0.001842,0.001617,0.001731
|
||||||
|
481
osipovamd/docs/task1.py
Normal file
481
osipovamd/docs/task1.py
Normal file
|
|
@ -0,0 +1,481 @@
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import csv
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# Увеличиваем лимит рекурсии для BST
|
||||||
|
sys.setrecursionlimit(10000)
|
||||||
|
|
||||||
|
|
||||||
|
def ll_insert(head, name, phone):
|
||||||
|
current = head
|
||||||
|
while current:
|
||||||
|
if current['name'] == name:
|
||||||
|
current['phone'] = phone
|
||||||
|
return head
|
||||||
|
current = current['next']
|
||||||
|
new_node = {'name': name, 'phone': phone, 'next': head}
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
def ll_find(head, name):
|
||||||
|
current = head
|
||||||
|
while current:
|
||||||
|
if current['name'] == name:
|
||||||
|
return current['phone']
|
||||||
|
current = current['next']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ll_delete(head, name):
|
||||||
|
if not head:
|
||||||
|
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 hash_function(name, size):
|
||||||
|
return sum(ord(c) for c in name) % size
|
||||||
|
|
||||||
|
def ht_create(size=1000):
|
||||||
|
return [None] * size
|
||||||
|
|
||||||
|
def ht_insert(buckets, name, phone):
|
||||||
|
index = hash_function(name, len(buckets))
|
||||||
|
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||||
|
|
||||||
|
def ht_find(buckets, name):
|
||||||
|
index = hash_function(name, len(buckets))
|
||||||
|
return ll_find(buckets[index], name)
|
||||||
|
|
||||||
|
def ht_delete(buckets, name):
|
||||||
|
index = hash_function(name, len(buckets))
|
||||||
|
buckets[index] = ll_delete(buckets[index], name)
|
||||||
|
|
||||||
|
def ht_list_all(buckets):
|
||||||
|
records = []
|
||||||
|
for bucket in buckets:
|
||||||
|
current = bucket
|
||||||
|
while current:
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
records.sort(key=lambda x: x[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
def bst_insert(root, name, phone):
|
||||||
|
"""Итеративная вставка для избежания RecursionError"""
|
||||||
|
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
current = root
|
||||||
|
while True:
|
||||||
|
if name < current['name']:
|
||||||
|
if current['left'] is None:
|
||||||
|
current['left'] = new_node
|
||||||
|
break
|
||||||
|
current = current['left']
|
||||||
|
elif name > current['name']:
|
||||||
|
if current['right'] is None:
|
||||||
|
current['right'] = new_node
|
||||||
|
break
|
||||||
|
current = current['right']
|
||||||
|
else:
|
||||||
|
current['phone'] = phone
|
||||||
|
break
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
def bst_find(root, name):
|
||||||
|
current = root
|
||||||
|
while current:
|
||||||
|
if name == current['name']:
|
||||||
|
return current['phone']
|
||||||
|
elif name < current['name']:
|
||||||
|
current = current['left']
|
||||||
|
else:
|
||||||
|
current = current['right']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def bst_find_min(node):
|
||||||
|
current = node
|
||||||
|
while current and current['left']:
|
||||||
|
current = current['left']
|
||||||
|
return current
|
||||||
|
|
||||||
|
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']
|
||||||
|
elif root['right'] is None:
|
||||||
|
return root['left']
|
||||||
|
|
||||||
|
min_node = bst_find_min(root['right'])
|
||||||
|
root['name'] = min_node['name']
|
||||||
|
root['phone'] = min_node['phone']
|
||||||
|
root['right'] = bst_delete(root['right'], min_node['name'])
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
def bst_list_all(root):
|
||||||
|
records = []
|
||||||
|
stack = []
|
||||||
|
current = root
|
||||||
|
|
||||||
|
while stack or current:
|
||||||
|
while current:
|
||||||
|
stack.append(current)
|
||||||
|
current = current['left']
|
||||||
|
current = stack.pop()
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['right']
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
def copy_linked_list(head):
|
||||||
|
if not head:
|
||||||
|
return None
|
||||||
|
new_head = {'name': head['name'], 'phone': head['phone'], 'next': None}
|
||||||
|
current_new = new_head
|
||||||
|
current_old = head['next']
|
||||||
|
while current_old:
|
||||||
|
current_new['next'] = {'name': current_old['name'], 'phone': current_old['phone'], 'next': None}
|
||||||
|
current_new = current_new['next']
|
||||||
|
current_old = current_old['next']
|
||||||
|
return new_head
|
||||||
|
|
||||||
|
def copy_bst(node):
|
||||||
|
if not node:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
'name': node['name'],
|
||||||
|
'phone': node['phone'],
|
||||||
|
'left': copy_bst(node['left']),
|
||||||
|
'right': copy_bst(node['right'])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_test_data(N=10000):
|
||||||
|
|
||||||
|
names = [f"User_{i:05d}" for i in range(N)]
|
||||||
|
records = [(name, f"+7-999-{random.randint(1000000, 9999999)}") for name in names]
|
||||||
|
|
||||||
|
records_shuffled = records.copy()
|
||||||
|
random.shuffle(records_shuffled)
|
||||||
|
|
||||||
|
records_sorted = sorted(records, key=lambda x: x[0])
|
||||||
|
|
||||||
|
return records_shuffled, records_sorted
|
||||||
|
|
||||||
|
def get_test_queries(records, num_existing=100, num_nonexisting=10):
|
||||||
|
existing_names = [name for name, _ in random.sample(records, min(num_existing, len(records)))]
|
||||||
|
nonexisting_names = [f"None_{i:05d}" for i in range(num_nonexisting)]
|
||||||
|
|
||||||
|
queries = existing_names + nonexisting_names
|
||||||
|
random.shuffle(queries)
|
||||||
|
|
||||||
|
return queries
|
||||||
|
|
||||||
|
def get_delete_names(records, num_to_delete=50):
|
||||||
|
return [name for name, _ in random.sample(records, min(num_to_delete, len(records)))]
|
||||||
|
|
||||||
|
|
||||||
|
def measure_insertion(structure_type, records, repeats=3):
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for _ in range(repeats):
|
||||||
|
if structure_type == "LinkedList":
|
||||||
|
structure = None
|
||||||
|
insert_func = ll_insert
|
||||||
|
elif structure_type == "HashTable":
|
||||||
|
structure = ht_create(2000)
|
||||||
|
insert_func = ht_insert
|
||||||
|
elif structure_type == "BST":
|
||||||
|
structure = None
|
||||||
|
insert_func = bst_insert
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown structure: {structure_type}")
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name, phone in records:
|
||||||
|
if structure_type == "HashTable":
|
||||||
|
insert_func(structure, name, phone)
|
||||||
|
else:
|
||||||
|
structure = insert_func(structure, name, phone)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def measure_search(structure_type, structure, queries, repeats=3):
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for _ in range(repeats):
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name in queries:
|
||||||
|
if structure_type == "LinkedList":
|
||||||
|
ll_find(structure, name)
|
||||||
|
elif structure_type == "HashTable":
|
||||||
|
ht_find(structure, name)
|
||||||
|
elif structure_type == "BST":
|
||||||
|
bst_find(structure, name)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def measure_deletion(structure_type, structure, names_to_delete, repeats=3):
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for _ in range(repeats):
|
||||||
|
if structure_type == "LinkedList":
|
||||||
|
temp_structure = copy_linked_list(structure)
|
||||||
|
delete_func = ll_delete
|
||||||
|
|
||||||
|
elif structure_type == "HashTable":
|
||||||
|
temp_structure = structure.copy()
|
||||||
|
for i in range(len(temp_structure)):
|
||||||
|
if temp_structure[i]:
|
||||||
|
temp_structure[i] = copy_linked_list(temp_structure[i])
|
||||||
|
delete_func = ht_delete
|
||||||
|
|
||||||
|
elif structure_type == "BST":
|
||||||
|
temp_structure = copy_bst(structure)
|
||||||
|
delete_func = bst_delete
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name in names_to_delete:
|
||||||
|
if structure_type == "HashTable":
|
||||||
|
delete_func(temp_structure, name)
|
||||||
|
else:
|
||||||
|
temp_structure = delete_func(temp_structure, name)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def run_experiment(N=2000):
|
||||||
|
|
||||||
|
print(f"Генерация тестовых данных (N={N})...")
|
||||||
|
records_shuffled, records_sorted = generate_test_data(N)
|
||||||
|
|
||||||
|
queries = get_test_queries(records_shuffled, num_existing=100, num_nonexisting=10)
|
||||||
|
delete_names = get_delete_names(records_shuffled, num_to_delete=50)
|
||||||
|
|
||||||
|
structures = ["LinkedList", "HashTable", "BST"]
|
||||||
|
modes = ["случайный", "отсортированный"]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
print("\nНачало экспериментов:")
|
||||||
|
|
||||||
|
for structure in structures:
|
||||||
|
print(f"\nТестирование {structure}...")
|
||||||
|
|
||||||
|
for mode in modes:
|
||||||
|
print(f" Режим: {mode}")
|
||||||
|
records = records_shuffled if mode == "случайный" else records_sorted
|
||||||
|
|
||||||
|
print(f" Измерение вставки...")
|
||||||
|
try:
|
||||||
|
insert_times = measure_insertion(structure, records, repeats=3)
|
||||||
|
avg_insert = sum(insert_times) / len(insert_times)
|
||||||
|
except RecursionError:
|
||||||
|
print(f" ОШИБКА: Превышена глубина рекурсии при вставке в {structure} для {mode} режима")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f" Создание финальной структуры...")
|
||||||
|
if structure == "LinkedList":
|
||||||
|
final_structure = None
|
||||||
|
for name, phone in records:
|
||||||
|
final_structure = ll_insert(final_structure, name, phone)
|
||||||
|
elif structure == "HashTable":
|
||||||
|
final_structure = ht_create(2000)
|
||||||
|
for name, phone in records:
|
||||||
|
ht_insert(final_structure, name, phone)
|
||||||
|
elif structure == "BST":
|
||||||
|
final_structure = None
|
||||||
|
for name, phone in records:
|
||||||
|
final_structure = bst_insert(final_structure, name, phone)
|
||||||
|
|
||||||
|
print(f" Измерение поиска...")
|
||||||
|
search_times = measure_search(structure, final_structure, queries, repeats=3)
|
||||||
|
avg_search = sum(search_times) / len(search_times)
|
||||||
|
|
||||||
|
print(f" Измерение удаления...")
|
||||||
|
deletion_times = measure_deletion(structure, final_structure, delete_names, repeats=3)
|
||||||
|
avg_deletion = sum(deletion_times) / len(deletion_times)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"Структура": structure,
|
||||||
|
"Режим": mode,
|
||||||
|
"Операция": "вставка",
|
||||||
|
"Замеры": insert_times,
|
||||||
|
"Среднее": avg_insert
|
||||||
|
})
|
||||||
|
results.append({
|
||||||
|
"Структура": structure,
|
||||||
|
"Режим": mode,
|
||||||
|
"Операция": "поиск",
|
||||||
|
"Замеры": search_times,
|
||||||
|
"Среднее": avg_search
|
||||||
|
})
|
||||||
|
results.append({
|
||||||
|
"Структура": structure,
|
||||||
|
"Режим": mode,
|
||||||
|
"Операция": "удаление",
|
||||||
|
"Замеры": deletion_times,
|
||||||
|
"Среднее": avg_deletion
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f" Вставка: {avg_insert:.6f} сек")
|
||||||
|
print(f" Поиск: {avg_search:.6f} сек")
|
||||||
|
print(f" Удаление: {avg_deletion:.6f} сек")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def save_to_csv(results, filename="results.csv"):
|
||||||
|
save_dir = "/Users/mariiaos/2026-rff_mp/osipovamd/docs"
|
||||||
|
filepath = os.path.join(save_dir, filename)
|
||||||
|
|
||||||
|
with open(filepath, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["Структура", "Режим", "Операция", "Замер1", "Замер2", "Замер3", "Среднее"])
|
||||||
|
|
||||||
|
for res in results:
|
||||||
|
row = [
|
||||||
|
res["Структура"],
|
||||||
|
res["Режим"],
|
||||||
|
res["Операция"],
|
||||||
|
*[f"{t:.6f}" for t in res["Замеры"]],
|
||||||
|
f"{res['Среднее']:.6f}"
|
||||||
|
]
|
||||||
|
writer.writerow(row)
|
||||||
|
|
||||||
|
print(f"\nРезультаты сохранены в: {filepath}")
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
def plot_results(results):
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("Нет данных для построения графиков!")
|
||||||
|
return
|
||||||
|
|
||||||
|
plt.style.use('seaborn-v0_8-darkgrid')
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
|
|
||||||
|
operations = ["вставка", "поиск", "удаление"]
|
||||||
|
structures = ["LinkedList", "HashTable", "BST"]
|
||||||
|
modes = ["случайный", "отсортированный"]
|
||||||
|
|
||||||
|
colors = {'LinkedList': '#FF6B6B', 'HashTable': '#4ECDC4', 'BST': '#45B7D1'}
|
||||||
|
|
||||||
|
for idx, operation in enumerate(operations):
|
||||||
|
ax = axes[idx]
|
||||||
|
|
||||||
|
x = np.arange(len(modes))
|
||||||
|
width = 0.25
|
||||||
|
multiplier = 0
|
||||||
|
|
||||||
|
for structure in structures:
|
||||||
|
values = []
|
||||||
|
for mode in modes:
|
||||||
|
found = False
|
||||||
|
for res in results:
|
||||||
|
if (res["Структура"] == structure and
|
||||||
|
res["Режим"] == mode and
|
||||||
|
res["Операция"] == operation):
|
||||||
|
values.append(res["Среднее"])
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
values.append(0)
|
||||||
|
|
||||||
|
if max(values) > 0:
|
||||||
|
offset = width * multiplier
|
||||||
|
bars = ax.bar(x + offset, values, width, label=structure, color=colors[structure])
|
||||||
|
multiplier += 1
|
||||||
|
|
||||||
|
ax.set_xlabel('Режим данных', fontsize=12)
|
||||||
|
ax.set_ylabel('Время (секунды)', fontsize=12)
|
||||||
|
ax.set_title(f'{operation.capitalize()}', fontsize=14, fontweight='bold')
|
||||||
|
ax.set_xticks(x + width)
|
||||||
|
ax.set_xticklabels(modes)
|
||||||
|
ax.legend(loc='upper left')
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
plt.suptitle('Сравнение производительности структур данных',
|
||||||
|
fontsize=16, fontweight='bold')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('performance_comparison.png', dpi=300, bbox_inches='tight')
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("тестирование производительности структур данных")
|
||||||
|
|
||||||
|
results = run_experiment(N=1000)
|
||||||
|
save_to_csv(results)
|
||||||
|
|
||||||
|
if results:
|
||||||
|
print("\nПостроение графиков...")
|
||||||
|
plot_results(results)
|
||||||
|
|
||||||
|
print("Сводная таблица результатов (среднее время в секундах)")
|
||||||
|
print(f"{'Структура':<12} {'Режим':<12} {'Вставка':<10} {'Поиск':<10} {'Удаление':<10}")
|
||||||
|
|
||||||
|
|
||||||
|
for structure in ["LinkedList", "HashTable", "BST"]:
|
||||||
|
for mode in ["случайный", "отсортированный"]:
|
||||||
|
insert_time = search_time = delete_time = 0
|
||||||
|
for res in results:
|
||||||
|
if res["Структура"] == structure and res["Режим"] == mode:
|
||||||
|
if res["Операция"] == "вставка":
|
||||||
|
insert_time = res["Среднее"]
|
||||||
|
elif res["Операция"] == "поиск":
|
||||||
|
search_time = res["Среднее"]
|
||||||
|
elif res["Операция"] == "удаление":
|
||||||
|
delete_time = res["Среднее"]
|
||||||
|
|
||||||
|
if insert_time > 0 or search_time > 0 or delete_time > 0:
|
||||||
|
print(f"{structure:<12} {mode:<12} {insert_time:<10.6f} {search_time:<10.6f} {delete_time:<10.6f}")
|
||||||
|
else:
|
||||||
|
print("\nЭксперимент не дал результатов из-за ошибок.")
|
||||||
|
|
||||||
|
print("\nЭксперимент завершён!")
|
||||||
283
osipovamd/maze_project/experiment.py
Normal file
283
osipovamd/maze_project/experiment.py
Normal file
|
|
@ -0,0 +1,283 @@
|
||||||
|
"""
|
||||||
|
Экспериментальный запуск для всех лабиринтов и алгоритмов
|
||||||
|
Создание CSV и графиков
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from datetime import datetime
|
||||||
|
from maze_model import Maze
|
||||||
|
from maze_builder import TextFileMazeBuilder
|
||||||
|
from pathfinding_strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||||
|
from maze_solver import MazeSolver, SearchStats
|
||||||
|
|
||||||
|
|
||||||
|
class ExperimentRunner:
|
||||||
|
def __init__(self):
|
||||||
|
self.all_results = []
|
||||||
|
self.labirints = {
|
||||||
|
'labirint1.txt': 'Маленький (10x10) с простым путём',
|
||||||
|
'labirint2.txt': 'Средний (50x50) с тупиками',
|
||||||
|
'labirint3.txt': 'Большой (100x100) запутанный',
|
||||||
|
'labirint4.txt': 'Пустой (20x20) без стен',
|
||||||
|
'labirint5.txt': 'Без выхода (20x20)'
|
||||||
|
}
|
||||||
|
self.strategies = [
|
||||||
|
BFSStrategy(),
|
||||||
|
DFSStrategy(),
|
||||||
|
AStarStrategy()
|
||||||
|
]
|
||||||
|
|
||||||
|
def run_all_experiments(self):
|
||||||
|
"""Запускает эксперименты для всех лабиринтов и алгоритмов"""
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("ЗАПУСК ЭКСПЕРИМЕНТОВ")
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
|
||||||
|
for filename, description in self.labirints.items():
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
print(f"\n⚠️ Файл {filename} не найден, пропускаем...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"\n📁 Лабиринт: {description}")
|
||||||
|
print(f" Файл: {filename}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
maze = builder.build_from_file(filename)
|
||||||
|
maze_name = filename.replace('.txt', '')
|
||||||
|
|
||||||
|
for strategy in self.strategies:
|
||||||
|
print(f" Тестирование {strategy.get_name()}...", end=" ", flush=True)
|
||||||
|
|
||||||
|
solver = MazeSolver(maze, maze_name, strategy)
|
||||||
|
path, stats = solver.solve_with_stats()
|
||||||
|
|
||||||
|
self.all_results.append({
|
||||||
|
'лабиринт': description,
|
||||||
|
'стратегия': stats.algorithm_name,
|
||||||
|
'время_мс': stats.execution_time_ms,
|
||||||
|
'посещено_клеток': stats.visited_cells,
|
||||||
|
'длина_пути': stats.path_length,
|
||||||
|
'путь_найден': 'Да' if stats.path_found else 'Нет',
|
||||||
|
'размер': stats.maze_size
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"готово! время={stats.execution_time_ms:.3f}мс, путь={stats.path_length}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Ошибка: {e}")
|
||||||
|
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("ЭКСПЕРИМЕНТЫ ЗАВЕРШЕНЫ")
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
def save_to_csv(self, filename="experiment_results.csv"):
|
||||||
|
"""Сохраняет результаты в CSV"""
|
||||||
|
if not self.all_results:
|
||||||
|
print("Нет результатов для сохранения!")
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
|
||||||
|
fieldnames = ['лабиринт', 'стратегия', 'время_мс', 'посещено_клеток', 'длина_пути', 'путь_найден', 'размер']
|
||||||
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(self.all_results)
|
||||||
|
|
||||||
|
print(f"\n✅ Результаты сохранены в {filename}")
|
||||||
|
|
||||||
|
# Показываем содержимое CSV
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("СОДЕРЖИМОЕ CSV ФАЙЛА:")
|
||||||
|
print("="*70)
|
||||||
|
with open(filename, 'r', encoding='utf-8-sig') as f:
|
||||||
|
print(f.read())
|
||||||
|
|
||||||
|
def create_charts(self):
|
||||||
|
"""Создаёт графики для каждого лабиринта"""
|
||||||
|
try:
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("ПОСТРОЕНИЕ ГРАФИКОВ")
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
# Группируем результаты по лабиринтам
|
||||||
|
results_by_maze = {}
|
||||||
|
for result in self.all_results:
|
||||||
|
maze = result['лабиринт']
|
||||||
|
if maze not in results_by_maze:
|
||||||
|
results_by_maze[maze] = []
|
||||||
|
results_by_maze[maze].append(result)
|
||||||
|
|
||||||
|
# Для каждого лабиринта создаём отдельный график
|
||||||
|
for maze_name, maze_results in results_by_maze.items():
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
|
fig.suptitle(f'Сравнение алгоритмов: {maze_name}', fontsize=14, fontweight='bold')
|
||||||
|
|
||||||
|
algorithms = [r['стратегия'] for r in maze_results]
|
||||||
|
|
||||||
|
# График 1: Время выполнения
|
||||||
|
times = [r['время_мс'] for r in maze_results]
|
||||||
|
bars1 = axes[0].bar(algorithms, times, color=['blue', 'green', 'red'])
|
||||||
|
axes[0].set_ylabel('Время (мс)')
|
||||||
|
axes[0].set_title('Время выполнения')
|
||||||
|
axes[0].tick_params(axis='x', rotation=15)
|
||||||
|
for bar, val in zip(bars1, times):
|
||||||
|
axes[0].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
|
||||||
|
f'{val:.3f}', ha='center', va='bottom', fontsize=9)
|
||||||
|
|
||||||
|
# График 2: Посещённые клетки
|
||||||
|
visited = [r['посещено_клеток'] for r in maze_results]
|
||||||
|
bars2 = axes[1].bar(algorithms, visited, color=['blue', 'green', 'red'])
|
||||||
|
axes[1].set_ylabel('Количество клеток')
|
||||||
|
axes[1].set_title('Посещённые клетки')
|
||||||
|
axes[1].tick_params(axis='x', rotation=15)
|
||||||
|
for bar, val in zip(bars2, visited):
|
||||||
|
axes[1].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
|
||||||
|
f'{val:.0f}', ha='center', va='bottom', fontsize=9)
|
||||||
|
|
||||||
|
# График 3: Длина пути
|
||||||
|
lengths = [r['длина_пути'] for r in maze_results]
|
||||||
|
bars3 = axes[2].bar(algorithms, lengths, color=['blue', 'green', 'red'])
|
||||||
|
axes[2].set_ylabel('Шагов')
|
||||||
|
axes[2].set_title('Длина найденного пути')
|
||||||
|
axes[2].tick_params(axis='x', rotation=15)
|
||||||
|
for bar, val in zip(bars3, lengths):
|
||||||
|
axes[2].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
|
||||||
|
f'{val:.0f}', ha='center', va='bottom', fontsize=9)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
# Сохраняем график
|
||||||
|
safe_name = maze_name.replace(' ', '_').replace('(', '').replace(')', '').replace('×', 'x')
|
||||||
|
filename = f"chart_{safe_name}.png"
|
||||||
|
plt.savefig(filename, dpi=150, bbox_inches='tight')
|
||||||
|
print(f"✅ Сохранён: {filename}")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
# Общий сводный график
|
||||||
|
self._create_summary_chart()
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
print("\n⚠️ Для построения графиков установите matplotlib:")
|
||||||
|
print(" pip install matplotlib numpy")
|
||||||
|
|
||||||
|
def _create_summary_chart(self):
|
||||||
|
"""Создаёт сводный график по всем лабиринтам"""
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
|
||||||
|
fig.suptitle('Сводное сравнение алгоритмов по всем лабиринтам', fontsize=14, fontweight='bold')
|
||||||
|
|
||||||
|
# Получаем уникальные лабиринты и алгоритмы
|
||||||
|
mazes = list(set([r['лабиринт'] for r in self.all_results]))
|
||||||
|
algorithms = ['BFS (Поиск в ширину)', 'DFS (Поиск в глубину)', 'A* (A-Star)']
|
||||||
|
|
||||||
|
# 1. Время по лабиринтам
|
||||||
|
ax1 = axes[0, 0]
|
||||||
|
x = np.arange(len(mazes))
|
||||||
|
width = 0.25
|
||||||
|
|
||||||
|
for i, algo in enumerate(algorithms):
|
||||||
|
times = []
|
||||||
|
for maze in mazes:
|
||||||
|
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
|
||||||
|
times.append(result['время_мс'] if result else 0)
|
||||||
|
bars = ax1.bar(x + (i - 1) * width, times, width, label=algo)
|
||||||
|
for bar, val in zip(bars, times):
|
||||||
|
if val > 0:
|
||||||
|
ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
|
||||||
|
f'{val:.1f}', ha='center', va='bottom', fontsize=7)
|
||||||
|
|
||||||
|
ax1.set_xlabel('Лабиринт')
|
||||||
|
ax1.set_ylabel('Время (мс)')
|
||||||
|
ax1.set_title('Сравнение времени выполнения')
|
||||||
|
ax1.set_xticks(x)
|
||||||
|
ax1.set_xticklabels([m[:20] for m in mazes], rotation=45, ha='right')
|
||||||
|
ax1.legend()
|
||||||
|
|
||||||
|
# 2. Посещённые клетки
|
||||||
|
ax2 = axes[0, 1]
|
||||||
|
for i, algo in enumerate(algorithms):
|
||||||
|
visited = []
|
||||||
|
for maze in mazes:
|
||||||
|
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
|
||||||
|
visited.append(result['посещено_клеток'] if result else 0)
|
||||||
|
bars = ax2.bar(x + (i - 1) * width, visited, width, label=algo)
|
||||||
|
for bar, val in zip(bars, visited):
|
||||||
|
if val > 0:
|
||||||
|
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
|
||||||
|
f'{val:.0f}', ha='center', va='bottom', fontsize=7)
|
||||||
|
|
||||||
|
ax2.set_xlabel('Лабиринт')
|
||||||
|
ax2.set_ylabel('Посещённые клетки')
|
||||||
|
ax2.set_title('Сравнение посещённых клеток')
|
||||||
|
ax2.set_xticks(x)
|
||||||
|
ax2.set_xticklabels([m[:20] for m in mazes], rotation=45, ha='right')
|
||||||
|
ax2.legend()
|
||||||
|
|
||||||
|
# 3. Длина пути
|
||||||
|
ax3 = axes[1, 0]
|
||||||
|
for i, algo in enumerate(algorithms):
|
||||||
|
lengths = []
|
||||||
|
for maze in mazes:
|
||||||
|
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
|
||||||
|
lengths.append(result['длина_пути'] if result else 0)
|
||||||
|
bars = ax3.bar(x + (i - 1) * width, lengths, width, label=algo)
|
||||||
|
for bar, val in zip(bars, lengths):
|
||||||
|
if val > 0:
|
||||||
|
ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
|
||||||
|
f'{val:.0f}', ha='center', va='bottom', fontsize=7)
|
||||||
|
|
||||||
|
ax3.set_xlabel('Лабиринт')
|
||||||
|
ax3.set_ylabel('Длина пути (шагов)')
|
||||||
|
ax3.set_title('Сравнение длины пути')
|
||||||
|
ax3.set_xticks(x)
|
||||||
|
ax3.set_xticklabels([m[:20] for m in mazes], rotation=45, ha='right')
|
||||||
|
ax3.legend()
|
||||||
|
|
||||||
|
# 4. Таблица результатов
|
||||||
|
ax4 = axes[1, 1]
|
||||||
|
ax4.axis('tight')
|
||||||
|
ax4.axis('off')
|
||||||
|
|
||||||
|
# Создаём таблицу
|
||||||
|
table_data = []
|
||||||
|
for maze in mazes:
|
||||||
|
row = [maze[:25]]
|
||||||
|
for algo in algorithms:
|
||||||
|
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
|
||||||
|
if result:
|
||||||
|
row.append(f"{result['время_мс']:.1f}мс")
|
||||||
|
else:
|
||||||
|
row.append("-")
|
||||||
|
table_data.append(row)
|
||||||
|
|
||||||
|
columns = ['Лабиринт', 'BFS', 'DFS', 'A*']
|
||||||
|
table = ax4.table(cellText=table_data, colLabels=columns, cellLoc='center', loc='center')
|
||||||
|
table.auto_set_font_size(False)
|
||||||
|
table.set_fontsize(9)
|
||||||
|
table.scale(1.2, 1.5)
|
||||||
|
ax4.set_title('Сводная таблица (время в мс)', fontsize=10)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('summary_chart.png', dpi=150, bbox_inches='tight')
|
||||||
|
print("Сохранён: summary_chart.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
runner = ExperimentRunner()
|
||||||
|
runner.run_all_experiments()
|
||||||
|
runner.save_to_csv("experiment_results.csv")
|
||||||
|
runner.create_charts()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
18
osipovamd/maze_project/experiment_results.csv
Normal file
18
osipovamd/maze_project/experiment_results.csv
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
лабиринт,стратегия,время_мс,длина_пути,путь_найден,размер,дата_время
|
||||||
|
labirint1,BFS (Поиск в ширину),0.211,14,Да,10x10,2026-05-27 22:26:56
|
||||||
|
labirint2,BFS (Поиск в ширину),0.858,0,Нет,50x51,2026-05-27 22:27:10
|
||||||
|
labirint3,BFS (Поиск в ширину),0.055,0,Нет,100x100,2026-05-27 22:27:26
|
||||||
|
labirint4,BFS (Поиск в ширину),1.326,35,Да,20x20,2026-05-27 22:27:35
|
||||||
|
labirint5,BFS (Поиск в ширину),0.373,36,Да,21x20,2026-05-27 22:27:44
|
||||||
|
labirint1,DFS (Поиск в глубину),0.135,14,Да,10x10,2026-05-27 22:28:16
|
||||||
|
labirint2,DFS (Поиск в глубину),0.797,0,Нет,50x51,2026-05-27 22:28:25
|
||||||
|
labirint3,DFS (Поиск в глубину),0.047,0,Нет,100x100,2026-05-27 22:28:31
|
||||||
|
labirint4,DFS (Поиск в глубину),0.88,171,Да,20x20,2026-05-27 22:28:36
|
||||||
|
labirint5,DFS (Поиск в глубину),0.772,36,Да,21x20,2026-05-27 22:28:41
|
||||||
|
labirint1,A* (A-Star),0.311,14,Да,10x10,2026-05-27 22:28:45
|
||||||
|
labirint2,A* (A-Star),1.318,0,Нет,50x51,2026-05-27 22:28:50
|
||||||
|
labirint3,A* (A-Star),0.055,0,Нет,100x100,2026-05-27 22:28:55
|
||||||
|
labirint4,A* (A-Star),2.301,35,Да,20x20,2026-05-27 22:29:00
|
||||||
|
labirint5,A* (A-Star),0.684,36,Да,21x20,2026-05-27 22:29:04
|
||||||
|
labirint1,A* (A-Star),0.316,14,Да,10x10,2026-05-27 22:36:50
|
||||||
|
labirint1,DFS (Поиск в глубину),0.133,14,Да,10x10,2026-05-27 22:41:42
|
||||||
|
10
osipovamd/maze_project/labirint1.txt
Normal file
10
osipovamd/maze_project/labirint1.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
##########
|
||||||
|
#S #
|
||||||
|
# ##### #
|
||||||
|
# # #
|
||||||
|
# ### # #
|
||||||
|
# # # #
|
||||||
|
# # ### #
|
||||||
|
# # #
|
||||||
|
# #####E#
|
||||||
|
##########
|
||||||
51
osipovamd/maze_project/labirint2.txt
Normal file
51
osipovamd/maze_project/labirint2.txt
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
##################################################
|
||||||
|
#S #
|
||||||
|
# ############################################# #
|
||||||
|
# # # #
|
||||||
|
# # ######################################### # #
|
||||||
|
# # # # # #
|
||||||
|
# # # ##################################### # # #
|
||||||
|
# # # # # # # #
|
||||||
|
# # # # ################################# # # # #
|
||||||
|
# # # # # # # # # #
|
||||||
|
# # # # # ############################# # # # # #
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
# # # # # # ######################### # # # # # #
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
# # # # # # # ##################### # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # ################# # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # ############# # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # ######### # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # ##### # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # ##### # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # ######### # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # ############# # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # ################# # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # ##################### # # # # # # #
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
# # # # # # ######################### # # # # # #
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
# # # # # ############################# # # # # #
|
||||||
|
# # # # # # # # # #
|
||||||
|
# # # # ################################# # # # #
|
||||||
|
# # # # # # # #
|
||||||
|
# # # ##################################### # # #
|
||||||
|
# # # # # #
|
||||||
|
# # ######################################### # #
|
||||||
|
# # # #
|
||||||
|
# ############################################# #
|
||||||
|
# #
|
||||||
|
##################################################
|
||||||
|
# E#
|
||||||
|
##################################################
|
||||||
100
osipovamd/maze_project/labirint3.txt
Normal file
100
osipovamd/maze_project/labirint3.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
|
##S# # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
####################################################################################################
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
####################################################################################################
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
####################################################################################################
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
####################################################################################################
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
####################################################################################################
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
####################################################################################################
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
####################################################################################################
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
####################################################################################################
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
####################################################################################################
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
####################################################################################################
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
####################################################################################################
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
####################################################################################################
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
####################################################################################################
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
|
||||||
|
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
|
||||||
|
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
|
||||||
|
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # E#
|
||||||
|
####################################################################################################
|
||||||
20
osipovamd/maze_project/labirint4.txt
Normal file
20
osipovamd/maze_project/labirint4.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
####################
|
||||||
|
#S #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# E#
|
||||||
|
####################
|
||||||
20
osipovamd/maze_project/labirint5.txt
Normal file
20
osipovamd/maze_project/labirint5.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
####################
|
||||||
|
#S #
|
||||||
|
# ############### #
|
||||||
|
# # # #
|
||||||
|
# # ########### # #
|
||||||
|
# # # # # #
|
||||||
|
# # # ####### # # #
|
||||||
|
# # # # # # # #
|
||||||
|
# # # # ### # # # #
|
||||||
|
# # # # # # # # #
|
||||||
|
# # # # ##### # # #
|
||||||
|
# # # # # # #
|
||||||
|
# # # ######### # #
|
||||||
|
# # # # #
|
||||||
|
# # ############# #
|
||||||
|
# # #
|
||||||
|
# ###################
|
||||||
|
# #
|
||||||
|
# E#
|
||||||
|
####################
|
||||||
161
osipovamd/maze_project/main.py
Normal file
161
osipovamd/maze_project/main.py
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from datetime import datetime
|
||||||
|
from maze_model import Maze
|
||||||
|
from maze_builder import TextFileMazeBuilder
|
||||||
|
from pathfinding_strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||||
|
from maze_solver import MazeSolver
|
||||||
|
|
||||||
|
|
||||||
|
def get_maze_file():
|
||||||
|
maze_files = [f for f in os.listdir('.') if f.endswith('.txt') and f != 'experiment_results.csv']
|
||||||
|
|
||||||
|
if not maze_files:
|
||||||
|
print("\nНет файлов лабиринтов! Поместите файлы labirint1.txt и т.д. в папку.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print("\nДоступные файлы лабиринтов:")
|
||||||
|
for i, f in enumerate(maze_files, 1):
|
||||||
|
print(f" {i}. {f}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = input(f"\nВыберите файл (1-{len(maze_files)}): ").strip()
|
||||||
|
try:
|
||||||
|
idx = int(choice) - 1
|
||||||
|
if 0 <= idx < len(maze_files):
|
||||||
|
return maze_files[idx]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
print(f"Неверный выбор. Введите число от 1 до {len(maze_files)}")
|
||||||
|
|
||||||
|
|
||||||
|
def display_maze_with_path(maze: Maze, path=None):
|
||||||
|
print("\n+" + "-" * maze.width + "+")
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
row = "|"
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
if cell == maze.start_cell:
|
||||||
|
row += "S"
|
||||||
|
elif cell == maze.exit_cell:
|
||||||
|
row += "E"
|
||||||
|
elif path and cell in path:
|
||||||
|
row += "."
|
||||||
|
elif cell.is_wall:
|
||||||
|
row += "#"
|
||||||
|
else:
|
||||||
|
row += " "
|
||||||
|
row += "|"
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
print("+" + "-" * maze.width + "+")
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_csv(maze_name: str, algorithm_name: str, time_ms: float, path_length: int, path_found: bool, maze_size: str):
|
||||||
|
csv_filename = "experiment_results.csv"
|
||||||
|
|
||||||
|
file_exists = os.path.exists(csv_filename)
|
||||||
|
|
||||||
|
with open(csv_filename, 'a', newline='', encoding='utf-8-sig') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
|
||||||
|
if not file_exists:
|
||||||
|
writer.writerow(['лабиринт', 'стратегия', 'время_мс', 'длина_пути', 'путь_найден', 'размер', 'дата_время'])
|
||||||
|
|
||||||
|
writer.writerow([
|
||||||
|
maze_name,
|
||||||
|
algorithm_name,
|
||||||
|
round(time_ms, 3),
|
||||||
|
path_length,
|
||||||
|
'Да' if path_found else 'Нет',
|
||||||
|
maze_size,
|
||||||
|
datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
])
|
||||||
|
|
||||||
|
print(f"Результат сохранён в {csv_filename}")
|
||||||
|
|
||||||
|
|
||||||
|
def print_all_results():
|
||||||
|
csv_filename = "experiment_results.csv"
|
||||||
|
|
||||||
|
with open(csv_filename, 'r', encoding='utf-8-sig') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
headers = next(reader)
|
||||||
|
print(f"{headers[0]:<25} {headers[1]:<22} {headers[2]:<10} {headers[3]:<10} {headers[4]:<8} {headers[5]:<10}")
|
||||||
|
for row in reader:
|
||||||
|
print(f"{row[0]:<25} {row[1]:<22} {row[2]:<10} {row[3]:<10} {row[4]:<8} {row[5]:<10}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
filename = get_maze_file()
|
||||||
|
|
||||||
|
try:
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
maze = builder.build_from_file(filename)
|
||||||
|
|
||||||
|
maze_name = filename.replace('.txt', '')
|
||||||
|
maze_size = f"{maze.width}x{maze.height}"
|
||||||
|
|
||||||
|
print(f"\nЛабиринт загружен из файла: {filename}")
|
||||||
|
print(f" Размер: {maze_size}")
|
||||||
|
print(f" Старт: ({maze.start_cell.x}, {maze.start_cell.y})")
|
||||||
|
print(f" Выход: ({maze.exit_cell.x}, {maze.exit_cell.y})")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"\nФайл '{filename}' не найден!")
|
||||||
|
return
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"\nОшибка в файле лабиринта: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nЗагруженный лабиринт:")
|
||||||
|
maze.display()
|
||||||
|
|
||||||
|
print("ВЫБОР АЛГОРИТМА ПОИСКА")
|
||||||
|
print("1. BFS (Поиск в ширину)")
|
||||||
|
print("2. DFS (Поиск в глубину)")
|
||||||
|
print("3. A* (A-Star)")
|
||||||
|
|
||||||
|
|
||||||
|
choice = input("\nВыберите алгоритм (1-3): ").strip()
|
||||||
|
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
strategy = BFSStrategy()
|
||||||
|
elif choice == '2':
|
||||||
|
strategy = DFSStrategy()
|
||||||
|
elif choice == '3':
|
||||||
|
strategy = AStarStrategy()
|
||||||
|
else:
|
||||||
|
print("Неверный выбор!")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
solver = MazeSolver(maze, strategy)
|
||||||
|
|
||||||
|
path, stats = solver.solve_with_stats()
|
||||||
|
|
||||||
|
print(stats.detailed_report())
|
||||||
|
|
||||||
|
if path:
|
||||||
|
print("\nЛабиринт с найденным путём (точки):")
|
||||||
|
display_maze_with_path(maze, path)
|
||||||
|
else:
|
||||||
|
print("Путь не найден!")
|
||||||
|
|
||||||
|
# Сохраняем результат в CSV
|
||||||
|
save_to_csv(
|
||||||
|
maze_name=maze_name,
|
||||||
|
algorithm_name=stats.algorithm_name,
|
||||||
|
time_ms=stats.execution_time_ms,
|
||||||
|
path_length=stats.path_length,
|
||||||
|
path_found=stats.path_found,
|
||||||
|
maze_size=maze_size
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nПрограмма завершена!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
84
osipovamd/maze_project/maze_builder.py
Normal file
84
osipovamd/maze_project/maze_builder.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from maze_model import Maze
|
||||||
|
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def build_from_string(self, content: str) -> Maze:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
def __init__(self):
|
||||||
|
self._maze = None
|
||||||
|
self._lines = []
|
||||||
|
self._start_found = False
|
||||||
|
self._exit_found = False
|
||||||
|
|
||||||
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
|
try:
|
||||||
|
with open(filename, 'r', encoding='utf-8') as file:
|
||||||
|
content = file.read()
|
||||||
|
return self.build_from_string(content)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise FileNotFoundError(f"Файл не найден: {filename}")
|
||||||
|
|
||||||
|
def build_from_string(self, content: str) -> Maze:
|
||||||
|
self._reset()
|
||||||
|
self._lines = [line.rstrip('\n\r') for line in content.split('\n')]
|
||||||
|
while self._lines and not self._lines[-1].strip():
|
||||||
|
self._lines.pop()
|
||||||
|
|
||||||
|
if not self._lines:
|
||||||
|
raise ValueError("Пустой файл лабиринта")
|
||||||
|
|
||||||
|
height = len(self._lines)
|
||||||
|
width = max(len(line) for line in self._lines)
|
||||||
|
|
||||||
|
for i, line in enumerate(self._lines):
|
||||||
|
if len(line) != width:
|
||||||
|
self._lines[i] = line.ljust(width)
|
||||||
|
|
||||||
|
self._maze = Maze(width, height)
|
||||||
|
|
||||||
|
for y, line in enumerate(self._lines):
|
||||||
|
for x, char in enumerate(line):
|
||||||
|
self._parse_cell(x, y, char)
|
||||||
|
|
||||||
|
self._validate_maze()
|
||||||
|
return self._maze
|
||||||
|
|
||||||
|
def _reset(self):
|
||||||
|
self._maze = None
|
||||||
|
self._lines = []
|
||||||
|
self._start_found = False
|
||||||
|
self._exit_found = False
|
||||||
|
|
||||||
|
def _parse_cell(self, x: int, y: int, char: str):
|
||||||
|
cell = self._maze.get_cell(x, y)
|
||||||
|
if char == '#':
|
||||||
|
cell.is_wall = True
|
||||||
|
elif char == 'S':
|
||||||
|
if self._start_found:
|
||||||
|
raise ValueError(f"Найден второй старт в ({x}, {y})")
|
||||||
|
self._maze.set_start(x, y)
|
||||||
|
self._start_found = True
|
||||||
|
elif char == 'E':
|
||||||
|
if self._exit_found:
|
||||||
|
raise ValueError(f"Найден второй выход в ({x}, {y})")
|
||||||
|
self._maze.set_exit(x, y)
|
||||||
|
self._exit_found = True
|
||||||
|
elif char == ' ':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Неизвестный символ '{char}' в ({x}, {y})")
|
||||||
|
|
||||||
|
def _validate_maze(self):
|
||||||
|
if not self._start_found:
|
||||||
|
raise ValueError("В лабиринте не найден старт (символ 'S')")
|
||||||
|
if not self._exit_found:
|
||||||
|
raise ValueError("В лабиринте не найден выход (символ 'E')")
|
||||||
103
osipovamd/maze_project/maze_model.py
Normal file
103
osipovamd/maze_project/maze_model.py
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, x: int, y: int):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.is_wall = False
|
||||||
|
self.is_start = False
|
||||||
|
self.is_exit = False
|
||||||
|
|
||||||
|
def is_passable(self) -> bool:
|
||||||
|
return not self.is_wall
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
if self.is_start:
|
||||||
|
return "S"
|
||||||
|
elif self.is_exit:
|
||||||
|
return "E"
|
||||||
|
elif self.is_wall:
|
||||||
|
return "#"
|
||||||
|
else:
|
||||||
|
return "."
|
||||||
|
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
if not isinstance(other, Cell):
|
||||||
|
return False
|
||||||
|
return self.x == other.x and self.y == other.y
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.x, self.y))
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return (self.x, self.y) < (other.x, other.y)
|
||||||
|
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, width: int, height: int):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self._cells: List[List[Cell]] = []
|
||||||
|
self.start_cell: Optional[Cell] = None
|
||||||
|
self.exit_cell: Optional[Cell] = None
|
||||||
|
|
||||||
|
for y in range(height):
|
||||||
|
row = []
|
||||||
|
for x in range(width):
|
||||||
|
row.append(Cell(x, y))
|
||||||
|
self._cells.append(row)
|
||||||
|
|
||||||
|
def get_cell(self, x: int, y: int) -> Optional[Cell]:
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self._cells[y][x]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_neighbors(self, cell: Cell) -> List[Cell]:
|
||||||
|
neighbors = []
|
||||||
|
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||||||
|
for dx, dy in directions:
|
||||||
|
nx, ny = cell.x + dx, cell.y + dy
|
||||||
|
neighbor = self.get_cell(nx, ny)
|
||||||
|
if neighbor and neighbor.is_passable():
|
||||||
|
neighbors.append(neighbor)
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def set_wall(self, x: int, y: int, is_wall: bool = True) -> None:
|
||||||
|
cell = self.get_cell(x, y)
|
||||||
|
if cell:
|
||||||
|
cell.is_wall = is_wall
|
||||||
|
|
||||||
|
def set_start(self, x: int, y: int) -> None:
|
||||||
|
cell = self.get_cell(x, y)
|
||||||
|
if cell:
|
||||||
|
if self.start_cell:
|
||||||
|
self.start_cell.is_start = False
|
||||||
|
cell.is_start = True
|
||||||
|
self.start_cell = cell
|
||||||
|
|
||||||
|
def set_exit(self, x: int, y: int) -> None:
|
||||||
|
cell = self.get_cell(x, y)
|
||||||
|
if cell:
|
||||||
|
if self.exit_cell:
|
||||||
|
self.exit_cell.is_exit = False
|
||||||
|
cell.is_exit = True
|
||||||
|
self.exit_cell = cell
|
||||||
|
|
||||||
|
def display(self) -> None:
|
||||||
|
print("+" + "-" * self.width + "+")
|
||||||
|
for y in range(self.height):
|
||||||
|
row_str = "|"
|
||||||
|
for x in range(self.width):
|
||||||
|
cell = self._cells[y][x]
|
||||||
|
if cell.is_start:
|
||||||
|
row_str += "S"
|
||||||
|
elif cell.is_exit:
|
||||||
|
row_str += "E"
|
||||||
|
elif cell.is_wall:
|
||||||
|
row_str += "#"
|
||||||
|
else:
|
||||||
|
row_str += " "
|
||||||
|
row_str += "|"
|
||||||
|
print(row_str)
|
||||||
|
print("+" + "-" * self.width + "+")
|
||||||
71
osipovamd/maze_project/maze_solver.py
Normal file
71
osipovamd/maze_project/maze_solver.py
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import time
|
||||||
|
from typing import List, Optional
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from maze_model import Maze, Cell
|
||||||
|
from pathfinding_strategies import PathFindingStrategy
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SearchStats:
|
||||||
|
algorithm_name: str
|
||||||
|
path_length: int
|
||||||
|
execution_time_ms: float
|
||||||
|
path_found: bool
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.execution_time_ms = round(self.execution_time_ms, 3)
|
||||||
|
|
||||||
|
def summary(self) -> str:
|
||||||
|
if self.path_found:
|
||||||
|
return f"{self.algorithm_name}: путь найден | длина={self.path_length} | время={self.execution_time_ms} мс"
|
||||||
|
return f"{self.algorithm_name}: путь НЕ найден | время={self.execution_time_ms} мс"
|
||||||
|
|
||||||
|
def detailed_report(self) -> str:
|
||||||
|
separator = "=" * 50
|
||||||
|
report = f"\n{separator}\nОтчёт о поиске пути\n{separator}\n"
|
||||||
|
report += f"Алгоритм: {self.algorithm_name}\n"
|
||||||
|
report += f"Путь найден: {'Да' if self.path_found else 'Нет'}\n"
|
||||||
|
if self.path_found:
|
||||||
|
report += f"Длина пути: {self.path_length} шагов\n"
|
||||||
|
report += f"Время выполнения: {self.execution_time_ms} мс\n{separator}"
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze: Maze, strategy: PathFindingStrategy = None):
|
||||||
|
self._maze = maze
|
||||||
|
self._strategy = strategy
|
||||||
|
self._last_stats: Optional[SearchStats] = None
|
||||||
|
self._validate_maze()
|
||||||
|
|
||||||
|
def _validate_maze(self):
|
||||||
|
if not self._maze.start_cell:
|
||||||
|
raise ValueError("Лабиринт не имеет стартовой клетки")
|
||||||
|
if not self._maze.exit_cell:
|
||||||
|
raise ValueError("Лабиринт не имеет выходной клетки")
|
||||||
|
|
||||||
|
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
||||||
|
self._strategy = strategy
|
||||||
|
|
||||||
|
def solve(self) -> List[Cell]:
|
||||||
|
if self._strategy is None:
|
||||||
|
raise ValueError("Стратегия не установлена")
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
path = self._strategy.find_path(self._maze, self._maze.start_cell, self._maze.exit_cell)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
|
||||||
|
self._last_stats = SearchStats(
|
||||||
|
algorithm_name=self._strategy.get_name(),
|
||||||
|
path_length=len(path),
|
||||||
|
execution_time_ms=(end_time - start_time) * 1000,
|
||||||
|
path_found=len(path) > 0
|
||||||
|
)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def solve_with_stats(self) -> tuple:
|
||||||
|
path = self.solve()
|
||||||
|
return path, self._last_stats
|
||||||
|
|
||||||
|
def get_last_stats(self) -> Optional[SearchStats]:
|
||||||
|
return self._last_stats
|
||||||
123
osipovamd/maze_project/pathfinding_strategies.py
Normal file
123
osipovamd/maze_project/pathfinding_strategies.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
from collections import deque
|
||||||
|
import heapq
|
||||||
|
from maze_model import Maze, Cell
|
||||||
|
|
||||||
|
|
||||||
|
class PathFindingStrategy(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_name(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||||
|
if start == exit_cell:
|
||||||
|
return [start]
|
||||||
|
|
||||||
|
queue = deque([start])
|
||||||
|
came_from: Dict[Cell, Optional[Cell]] = {start: None}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if current == exit_cell:
|
||||||
|
return self._reconstruct_path(came_from, start, exit_cell)
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
if neighbor not in came_from:
|
||||||
|
came_from[neighbor] = current
|
||||||
|
queue.append(neighbor)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _reconstruct_path(self, came_from, start, exit_cell):
|
||||||
|
path = []
|
||||||
|
current = exit_cell
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = came_from.get(current)
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "BFS (Поиск в ширину)"
|
||||||
|
|
||||||
|
|
||||||
|
class DFSStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||||
|
if start == exit_cell:
|
||||||
|
return [start]
|
||||||
|
|
||||||
|
stack = [start]
|
||||||
|
came_from: Dict[Cell, Optional[Cell]] = {start: None}
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
if current == exit_cell:
|
||||||
|
return self._reconstruct_path(came_from, start, exit_cell)
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
if neighbor not in came_from:
|
||||||
|
came_from[neighbor] = current
|
||||||
|
stack.append(neighbor)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _reconstruct_path(self, came_from, start, exit_cell):
|
||||||
|
path = []
|
||||||
|
current = exit_cell
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = came_from.get(current)
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "DFS (Поиск в глубину)"
|
||||||
|
|
||||||
|
|
||||||
|
class AStarStrategy(PathFindingStrategy):
|
||||||
|
def _heuristic(self, cell: Cell, target: Cell) -> int:
|
||||||
|
return abs(cell.x - target.x) + abs(cell.y - target.y)
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||||
|
if start == exit_cell:
|
||||||
|
return [start]
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
open_set = [(0, counter, start)]
|
||||||
|
g_score: Dict[Cell, float] = {start: 0}
|
||||||
|
came_from: Dict[Cell, Optional[Cell]] = {start: None}
|
||||||
|
open_set_cells = {start}
|
||||||
|
|
||||||
|
while open_set:
|
||||||
|
_, _, current = heapq.heappop(open_set)
|
||||||
|
open_set_cells.remove(current)
|
||||||
|
|
||||||
|
if current == exit_cell:
|
||||||
|
return self._reconstruct_path(came_from, start, exit_cell)
|
||||||
|
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
tentative_g = g_score[current] + 1
|
||||||
|
if neighbor not in g_score or tentative_g < g_score[neighbor]:
|
||||||
|
came_from[neighbor] = current
|
||||||
|
g_score[neighbor] = tentative_g
|
||||||
|
f = tentative_g + self._heuristic(neighbor, exit_cell)
|
||||||
|
if neighbor not in open_set_cells:
|
||||||
|
counter += 1
|
||||||
|
heapq.heappush(open_set, (f, counter, neighbor))
|
||||||
|
open_set_cells.add(neighbor)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _reconstruct_path(self, came_from, start, exit_cell):
|
||||||
|
path = []
|
||||||
|
current = exit_cell
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = came_from.get(current)
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "A* (A-Star)"
|
||||||
Loading…
Reference in New Issue
Block a user