283 lines
13 KiB
Python
283 lines
13 KiB
Python
|
|
"""
|
|||
|
|
Экспериментальный запуск для всех лабиринтов и алгоритмов
|
|||
|
|
Создание 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()
|