[1] for 1-st ex #228
49
fomichevks/docs/data/empty.txt
Normal file
49
fomichevks/docs/data/empty.txt
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
########################################
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E#
|
||||
########################################
|
||||
13
fomichevks/docs/data/experiment_results.csv
Normal file
13
fomichevks/docs/data/experiment_results.csv
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
maze,strategy,time_ms,visited_cells,path_length,success_rate
|
||||
Small (10x10),BFS,0.052460000733844936,30.0,14.0,1.0
|
||||
Small (10x10),DFS,0.0480999966384843,32.0,14.0,1.0
|
||||
Small (10x10),A*,0.07206000154837966,23.0,14.0,1.0
|
||||
Medium (50x50),BFS,0.2786600001854822,182.0,92.0,1.0
|
||||
Medium (50x50),DFS,0.14713999989908189,93.0,92.0,1.0
|
||||
Medium (50x50),A*,0.5699400004232302,182.0,92.0,1.0
|
||||
Large (100x100),BFS,0.39185999776236713,201.0,149.0,1.0
|
||||
Large (100x100),DFS,0.2371800015680492,151.0,149.0,1.0
|
||||
Large (100x100),A*,0.5810399976326153,200.0,149.0,1.0
|
||||
Empty,BFS,3.187239999533631,1834.0,86.0,1.0
|
||||
Empty,DFS,1.9440599950030446,1797.0,922.0,1.0
|
||||
Empty,A*,6.751939994865097,1834.0,86.0,1.0
|
||||
|
BIN
fomichevks/docs/data/experiment_results.png
Normal file
BIN
fomichevks/docs/data/experiment_results.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
54
fomichevks/docs/data/large.txt
Normal file
54
fomichevks/docs/data/large.txt
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
####################################################################################################
|
||||
#S #
|
||||
# ################################################################################################ #
|
||||
# # # #
|
||||
# # ############################################################################################ # #
|
||||
# # # # # #
|
||||
# # # ######################################################################################## # # #
|
||||
# # # # # # # #
|
||||
# # # # #################################################################################### # # # #
|
||||
# # # # # # # # # #
|
||||
# # # # # ################################################################################ # # # # #
|
||||
# # # # # # # # # # # #
|
||||
# # # # # # ############################################################################ # # # # # #
|
||||
# # # # # # # # # # # # # #
|
||||
# # # # # # # ######################################################################## # # # # # # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # # # # # # # #################################################################### # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # ################################################################ # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # ############################################################ # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # ######################################################## # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # #################################################### # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # ################################################ # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # ############################################ # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # ######################################## # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # #################################### # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # ################################ # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # ############################ # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # ######################## # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # #################### # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # ################ # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # ############ # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # ######## # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #### # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #E#
|
||||
####################################################################################################
|
||||
532
fomichevks/docs/data/maze.py
Normal file
532
fomichevks/docs/data/maze.py
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
import sys
|
||||
from collections import deque
|
||||
import heapq
|
||||
import time
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
|
||||
DATA_PATH = r"C:\Users\Kirill\2026-rff_mp\fomichevks\docs\data"
|
||||
|
||||
|
||||
class Observer(ABC):
|
||||
@abstractmethod
|
||||
def update(self, event: str, data: Any = None):
|
||||
pass
|
||||
|
||||
|
||||
class Observable:
|
||||
def __init__(self):
|
||||
self._observers: List[Observer] = []
|
||||
|
||||
def attach(self, observer: Observer):
|
||||
self._observers.append(observer)
|
||||
|
||||
def detach(self, observer: Observer):
|
||||
self._observers.remove(observer)
|
||||
|
||||
def notify(self, event: str, data: Any = None):
|
||||
for observer in self._observers:
|
||||
observer.update(event, data)
|
||||
|
||||
|
||||
class Tile:
|
||||
def __init__(self, x: int, y: int):
|
||||
self._x = x
|
||||
self._y = y
|
||||
self._wall = False
|
||||
self._start = False
|
||||
self._exit = False
|
||||
|
||||
@property
|
||||
def x(self) -> int:
|
||||
return self._x
|
||||
|
||||
@property
|
||||
def y(self) -> int:
|
||||
return self._y
|
||||
|
||||
@property
|
||||
def is_wall(self) -> bool:
|
||||
return self._wall
|
||||
|
||||
@is_wall.setter
|
||||
def is_wall(self, v: bool):
|
||||
self._wall = v
|
||||
|
||||
@property
|
||||
def is_start(self) -> bool:
|
||||
return self._start
|
||||
|
||||
@is_start.setter
|
||||
def is_start(self, v: bool):
|
||||
self._start = v
|
||||
|
||||
@property
|
||||
def is_exit(self) -> bool:
|
||||
return self._exit
|
||||
|
||||
@is_exit.setter
|
||||
def is_exit(self, v: bool):
|
||||
self._exit = v
|
||||
|
||||
def passable(self) -> bool:
|
||||
return not self._wall
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._x, self._y))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Tile):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
|
||||
class Maze:
|
||||
def __init__(self, w: int, h: int):
|
||||
self._w = w
|
||||
self._h = h
|
||||
self._cells = [[Tile(x, y) for x in range(w)] for y in range(h)]
|
||||
self._start: Optional[Tile] = None
|
||||
self._exit: Optional[Tile] = None
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self._w
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
return self._h
|
||||
|
||||
@property
|
||||
def start(self) -> Optional[Tile]:
|
||||
return self._start
|
||||
|
||||
@property
|
||||
def exit(self) -> Optional[Tile]:
|
||||
return self._exit
|
||||
|
||||
def get_cell(self, x: int, y: int) -> Optional[Tile]:
|
||||
if 0 <= x < self._w and 0 <= y < self._h:
|
||||
return self._cells[y][x]
|
||||
return None
|
||||
|
||||
def set_cell(self, x: int, y: int, kind: str):
|
||||
c = self.get_cell(x, y)
|
||||
if not c:
|
||||
return
|
||||
if kind == 'wall':
|
||||
c.is_wall = True
|
||||
elif kind == 'start':
|
||||
if self._start:
|
||||
self._start.is_start = False
|
||||
c.is_start = True
|
||||
c.is_wall = False
|
||||
self._start = c
|
||||
elif kind == 'exit':
|
||||
if self._exit:
|
||||
self._exit.is_exit = False
|
||||
c.is_exit = True
|
||||
c.is_wall = False
|
||||
self._exit = c
|
||||
elif kind == 'path':
|
||||
c.is_wall = False
|
||||
|
||||
def neighbours(self, cell: Tile) -> List[Tile]:
|
||||
result = []
|
||||
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||
nx, ny = cell.x + dx, cell.y + dy
|
||||
nb = self.get_cell(nx, ny)
|
||||
if nb and nb.passable():
|
||||
result.append(nb)
|
||||
return result
|
||||
|
||||
|
||||
class MazeLoader(ABC):
|
||||
@abstractmethod
|
||||
def load(self, filename: str) -> Maze:
|
||||
pass
|
||||
|
||||
|
||||
class TextMazeLoader(MazeLoader):
|
||||
def load(self, filename: str) -> Maze:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||
|
||||
h = len(lines)
|
||||
w = max(len(line) for line in lines) if h else 0
|
||||
|
||||
start_count = 0
|
||||
exit_count = 0
|
||||
maze = Maze(w, h)
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
for x, ch in enumerate(line):
|
||||
if ch == '#':
|
||||
maze.set_cell(x, y, 'wall')
|
||||
elif ch == 'S':
|
||||
maze.set_cell(x, y, 'start')
|
||||
start_count += 1
|
||||
elif ch == 'E':
|
||||
maze.set_cell(x, y, 'exit')
|
||||
exit_count += 1
|
||||
else:
|
||||
maze.set_cell(x, y, 'path')
|
||||
|
||||
if start_count != 1 or exit_count != 1:
|
||||
raise ValueError(f"Maze must have one S and one E. Found: S={start_count}, E={exit_count}")
|
||||
|
||||
return maze
|
||||
|
||||
|
||||
class PathFinder(ABC):
|
||||
def __init__(self):
|
||||
self._visited = 0
|
||||
|
||||
@abstractmethod
|
||||
def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]:
|
||||
pass
|
||||
|
||||
def _reconstruct(self, parent: Dict[Tile, Optional[Tile]], start: Tile, goal: Tile) -> List[Tile]:
|
||||
path = []
|
||||
current = goal
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
path.reverse()
|
||||
return path if path and path[0] == start else []
|
||||
|
||||
@property
|
||||
def visited_count(self) -> int:
|
||||
return self._visited
|
||||
|
||||
|
||||
class BFS(PathFinder):
|
||||
def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]:
|
||||
queue = deque([start])
|
||||
parent = {start: None}
|
||||
visited = {start}
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
|
||||
if current == goal:
|
||||
self._visited = len(visited)
|
||||
return self._reconstruct(parent, start, goal)
|
||||
|
||||
for neighbor in maze.neighbours(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
parent[neighbor] = current
|
||||
queue.append(neighbor)
|
||||
|
||||
self._visited = len(visited)
|
||||
return []
|
||||
|
||||
|
||||
class DFS(PathFinder):
|
||||
def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]:
|
||||
stack = [start]
|
||||
parent = {start: None}
|
||||
visited = {start}
|
||||
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
|
||||
if current == goal:
|
||||
self._visited = len(visited)
|
||||
return self._reconstruct(parent, start, goal)
|
||||
|
||||
for neighbor in maze.neighbours(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
parent[neighbor] = current
|
||||
stack.append(neighbor)
|
||||
|
||||
self._visited = len(visited)
|
||||
return []
|
||||
|
||||
|
||||
class AStar(PathFinder):
|
||||
def _heuristic(self, cell: Tile, goal: Tile) -> int:
|
||||
return abs(cell.x - goal.x) + abs(cell.y - goal.y)
|
||||
|
||||
def find(self, maze: Maze, start: Tile, goal: Tile) -> List[Tile]:
|
||||
heap = []
|
||||
counter = 0
|
||||
start_f = self._heuristic(start, goal)
|
||||
heapq.heappush(heap, (start_f, counter, start))
|
||||
counter += 1
|
||||
|
||||
parent = {}
|
||||
g_score = {start: 0}
|
||||
f_score = {start: start_f}
|
||||
visited = set()
|
||||
|
||||
while heap:
|
||||
current_f, _, current = heapq.heappop(heap)
|
||||
visited.add(current)
|
||||
|
||||
if current == goal:
|
||||
self._visited = len(visited)
|
||||
return self._reconstruct(parent, start, goal)
|
||||
|
||||
if current_f > f_score.get(current, float('inf')):
|
||||
continue
|
||||
|
||||
for neighbor in maze.neighbours(current):
|
||||
tentative_g = g_score[current] + 1
|
||||
|
||||
if tentative_g < g_score.get(neighbor, float('inf')):
|
||||
parent[neighbor] = current
|
||||
g_score[neighbor] = tentative_g
|
||||
new_f = tentative_g + self._heuristic(neighbor, goal)
|
||||
f_score[neighbor] = new_f
|
||||
heapq.heappush(heap, (new_f, counter, neighbor))
|
||||
counter += 1
|
||||
|
||||
self._visited = len(visited)
|
||||
return []
|
||||
|
||||
|
||||
class MazeSolver(Observable):
|
||||
def __init__(self, maze: Maze):
|
||||
super().__init__()
|
||||
self._maze = maze
|
||||
self._algorithm: Optional[PathFinder] = None
|
||||
|
||||
def set_algorithm(self, algorithm: PathFinder):
|
||||
self._algorithm = algorithm
|
||||
|
||||
def solve(self) -> Optional[Dict[str, Any]]:
|
||||
if not self._algorithm:
|
||||
raise ValueError("Algorithm not set")
|
||||
|
||||
start_time = time.perf_counter()
|
||||
path = self._algorithm.find(self._maze, self._maze.start, self._maze.exit)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
elapsed_ms = (end_time - start_time) * 1000
|
||||
|
||||
return {
|
||||
'time_ms': elapsed_ms,
|
||||
'visited': self._algorithm.visited_count,
|
||||
'path_length': len(path),
|
||||
'path': path
|
||||
}
|
||||
|
||||
|
||||
class Command(ABC):
|
||||
@abstractmethod
|
||||
def execute(self) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def undo(self) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
class MoveCommand(Command):
|
||||
def __init__(self, player: 'Player', dx: int, dy: int, maze: Maze):
|
||||
self._player = player
|
||||
self._dx = dx
|
||||
self._dy = dy
|
||||
self._maze = maze
|
||||
self._executed = False
|
||||
|
||||
def execute(self) -> bool:
|
||||
new_x = self._player.position.x + self._dx
|
||||
new_y = self._player.position.y + self._dy
|
||||
target = self._maze.get_cell(new_x, new_y)
|
||||
|
||||
if target and target.passable():
|
||||
self._player.move_to(target)
|
||||
self._executed = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def undo(self) -> bool:
|
||||
if self._executed:
|
||||
self._player.undo()
|
||||
self._executed = False
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, start_tile: Tile):
|
||||
self._position = start_tile
|
||||
self._previous = None
|
||||
|
||||
@property
|
||||
def position(self) -> Tile:
|
||||
return self._position
|
||||
|
||||
def move_to(self, tile: Tile):
|
||||
self._previous = self._position
|
||||
self._position = tile
|
||||
|
||||
def undo(self):
|
||||
if self._previous:
|
||||
self._position, self._previous = self._previous, None
|
||||
|
||||
|
||||
class ConsoleView(Observer):
|
||||
def __init__(self, maze: Maze, player: Optional[Player] = None):
|
||||
self._maze = maze
|
||||
self._player = player
|
||||
self._current_path: List[Tile] = []
|
||||
|
||||
def update(self, event: str, data: Any = None):
|
||||
if event == "solving_finished":
|
||||
self._current_path = data.get('path', [])
|
||||
self._display_solution(data)
|
||||
|
||||
def _display_solution(self, stats: Dict):
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
print("=" * (self._maze.width * 2 + 4))
|
||||
print("MAZE SOLUTION")
|
||||
print("=" * (self._maze.width * 2 + 4))
|
||||
|
||||
for y in range(self._maze.height):
|
||||
print(" ", end='')
|
||||
for x in range(self._maze.width):
|
||||
cell = self._maze.get_cell(x, y)
|
||||
if cell == self._maze.start:
|
||||
print('S', end=' ')
|
||||
elif cell == self._maze.exit:
|
||||
print('E', end=' ')
|
||||
elif cell.is_wall:
|
||||
print('#', end=' ')
|
||||
elif self._current_path and cell in self._current_path:
|
||||
print('●', end=' ')
|
||||
else:
|
||||
print('.', end=' ')
|
||||
print()
|
||||
|
||||
print("=" * (self._maze.width * 2 + 4))
|
||||
print(f"Time: {stats['time_ms']:.3f} ms")
|
||||
print(f"Visited: {stats['visited']}")
|
||||
print(f"Path length: {stats['path_length']}")
|
||||
|
||||
def display_maze(self):
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
print("=" * (self._maze.width * 2 + 4))
|
||||
print("MAZE")
|
||||
print("=" * (self._maze.width * 2 + 4))
|
||||
|
||||
for y in range(self._maze.height):
|
||||
print(" ", end='')
|
||||
for x in range(self._maze.width):
|
||||
cell = self._maze.get_cell(x, y)
|
||||
if self._player and cell == self._player.position:
|
||||
print('P', end=' ')
|
||||
elif cell == self._maze.start:
|
||||
print('S', end=' ')
|
||||
elif cell == self._maze.exit:
|
||||
print('E', end=' ')
|
||||
elif cell.is_wall:
|
||||
print('#', end=' ')
|
||||
else:
|
||||
print('.', end=' ')
|
||||
print()
|
||||
|
||||
print("=" * (self._maze.width * 2 + 4))
|
||||
print("S - start E - exit # - wall . - path P - player")
|
||||
|
||||
|
||||
def interactive_mode(maze: Maze):
|
||||
player = Player(maze.start)
|
||||
view = ConsoleView(maze, player)
|
||||
view.display_maze()
|
||||
|
||||
solver = MazeSolver(maze)
|
||||
solver.attach(view)
|
||||
|
||||
commands_history: List[Command] = []
|
||||
|
||||
print("\nControls:")
|
||||
print("H (←) J (↓) K (↑) L (→) - move")
|
||||
print("U - undo")
|
||||
print("B - BFS")
|
||||
print("D - DFS")
|
||||
print("A - A*")
|
||||
print("Q - quit")
|
||||
print("\n" + "=" * 50)
|
||||
|
||||
while True:
|
||||
cmd = input("\n> ").lower().strip()
|
||||
|
||||
if cmd == 'q':
|
||||
break
|
||||
|
||||
elif cmd == 'b':
|
||||
solver.set_algorithm(BFS())
|
||||
result = solver.solve()
|
||||
if result:
|
||||
print(f"BFS: {result['time_ms']:.3f} ms, visited={result['visited']}, length={result['path_length']}")
|
||||
|
||||
elif cmd == 'd':
|
||||
solver.set_algorithm(DFS())
|
||||
result = solver.solve()
|
||||
if result:
|
||||
print(f"DFS: {result['time_ms']:.3f} ms, visited={result['visited']}, length={result['path_length']}")
|
||||
|
||||
elif cmd == 'a':
|
||||
solver.set_algorithm(AStar())
|
||||
result = solver.solve()
|
||||
if result:
|
||||
print(f"A*: {result['time_ms']:.3f} ms, visited={result['visited']}, length={result['path_length']}")
|
||||
|
||||
elif cmd in ['h', 'j', 'k', 'l']:
|
||||
dir_map = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)}
|
||||
dx, dy = dir_map[cmd]
|
||||
move = MoveCommand(player, dx, dy, maze)
|
||||
|
||||
if move.execute():
|
||||
commands_history.append(move)
|
||||
view.display_maze()
|
||||
|
||||
if player.position == maze.exit:
|
||||
print("\n*** YOU ESCAPED! ***")
|
||||
print(f"Total moves: {len(commands_history)}")
|
||||
break
|
||||
else:
|
||||
print("Blocked!")
|
||||
|
||||
elif cmd == 'u':
|
||||
if commands_history:
|
||||
last_command = commands_history.pop()
|
||||
last_command.undo()
|
||||
view.display_maze()
|
||||
print("Undo successful")
|
||||
else:
|
||||
print("Nothing to undo")
|
||||
|
||||
else:
|
||||
print("Unknown command")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
|
||||
import subprocess
|
||||
subprocess.run([sys.executable, 'plots.py'])
|
||||
return
|
||||
|
||||
loader = TextMazeLoader()
|
||||
|
||||
|
||||
maze_file = os.path.join(DATA_PATH, "maze1.txt")
|
||||
|
||||
if not os.path.exists(maze_file):
|
||||
print(f"ERROR: Maze file not found: {maze_file}")
|
||||
print(f"Please create maze1.txt in: {DATA_PATH}")
|
||||
return
|
||||
|
||||
maze = loader.load(maze_file)
|
||||
interactive_mode(maze)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
fomichevks/docs/data/maze1.txt
Normal file
10
fomichevks/docs/data/maze1.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
### #####
|
||||
# # E#
|
||||
# # # # ##
|
||||
# # #
|
||||
####### #
|
||||
# #
|
||||
# ###### #
|
||||
##########
|
||||
48
fomichevks/docs/data/medium.txt
Normal file
48
fomichevks/docs/data/medium.txt
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
##################################################
|
||||
#S #
|
||||
# ############################################# #
|
||||
# # # #
|
||||
# # ######################################### # #
|
||||
# # # # # #
|
||||
# # # ##################################### # # #
|
||||
# # # # # # # #
|
||||
# # # # ################################# # # # #
|
||||
# # # # # # # # # #
|
||||
# # # # # ############################# # # # # #
|
||||
# # # # # # # # # # # #
|
||||
# # # # # # ######################### # # # # # #
|
||||
# # # # # # # # # # # # # #
|
||||
# # # # # # # ##################### # # # # # # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # # # # # # # ################# # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # ############# # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # ######### # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # ##### # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # ##### # # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # ######### # # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # ############# # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # ################# # # # # # # # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # # # # # # ##################### # # # # # # #
|
||||
# # # # # # # # # # # # # #
|
||||
# # # # # # ######################### # # # # # #
|
||||
# # # # # # # # # # # #
|
||||
# # # # # ############################# # # # # #
|
||||
# # # # # # # # # #
|
||||
# # # # ################################# # # # #
|
||||
# # # # # # # #
|
||||
# # # ##################################### # # #
|
||||
# # # # # #
|
||||
# # ######################################### # #
|
||||
# # # #
|
||||
# ############################################# #
|
||||
# E#
|
||||
##################################################
|
||||
580
fomichevks/docs/data/plots.py
Normal file
580
fomichevks/docs/data/plots.py
Normal file
|
|
@ -0,0 +1,580 @@
|
|||
import csv
|
||||
import time
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from collections import deque
|
||||
import heapq
|
||||
|
||||
from maze import DATA_PATH
|
||||
|
||||
|
||||
|
||||
class Tile:
|
||||
def __init__(self, x: int, y: int):
|
||||
self._x = x
|
||||
self._y = y
|
||||
self._wall = False
|
||||
self._start = False
|
||||
self._exit = False
|
||||
|
||||
@property
|
||||
def x(self) -> int:
|
||||
return self._x
|
||||
|
||||
@property
|
||||
def y(self) -> int:
|
||||
return self._y
|
||||
|
||||
@property
|
||||
def is_wall(self) -> bool:
|
||||
return self._wall
|
||||
|
||||
@is_wall.setter
|
||||
def is_wall(self, v: bool):
|
||||
self._wall = v
|
||||
|
||||
@property
|
||||
def is_start(self) -> bool:
|
||||
return self._start
|
||||
|
||||
@is_start.setter
|
||||
def is_start(self, v: bool):
|
||||
self._start = v
|
||||
|
||||
@property
|
||||
def is_exit(self) -> bool:
|
||||
return self._exit
|
||||
|
||||
@is_exit.setter
|
||||
def is_exit(self, v: bool):
|
||||
self._exit = v
|
||||
|
||||
def passable(self) -> bool:
|
||||
return not self._wall
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._x, self._y))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Tile):
|
||||
return False
|
||||
return self._x == other._x and self._y == other._y
|
||||
|
||||
|
||||
class Maze:
|
||||
def __init__(self, w: int, h: int):
|
||||
self._w = w
|
||||
self._h = h
|
||||
self._cells = [[Tile(x, y) for x in range(w)] for y in range(h)]
|
||||
self._start = None
|
||||
self._exit = None
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self._w
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
return self._h
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return self._start
|
||||
|
||||
@property
|
||||
def exit(self):
|
||||
return self._exit
|
||||
|
||||
def get_cell(self, x: int, y: int):
|
||||
if 0 <= x < self._w and 0 <= y < self._h:
|
||||
return self._cells[y][x]
|
||||
return None
|
||||
|
||||
def set_cell(self, x: int, y: int, kind: str):
|
||||
c = self.get_cell(x, y)
|
||||
if not c:
|
||||
return
|
||||
if kind == 'wall':
|
||||
c.is_wall = True
|
||||
elif kind == 'start':
|
||||
if self._start:
|
||||
self._start.is_start = False
|
||||
c.is_start = True
|
||||
c.is_wall = False
|
||||
self._start = c
|
||||
elif kind == 'exit':
|
||||
if self._exit:
|
||||
self._exit.is_exit = False
|
||||
c.is_exit = True
|
||||
c.is_wall = False
|
||||
self._exit = c
|
||||
elif kind == 'path':
|
||||
c.is_wall = False
|
||||
|
||||
def neighbours(self, cell):
|
||||
result = []
|
||||
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||
nx, ny = cell.x + dx, cell.y + dy
|
||||
nb = self.get_cell(nx, ny)
|
||||
if nb and nb.passable():
|
||||
result.append(nb)
|
||||
return result
|
||||
|
||||
|
||||
class TextMazeLoader:
|
||||
def load(self, filename: str):
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||
|
||||
h = len(lines)
|
||||
w = max(len(line) for line in lines) if h else 0
|
||||
|
||||
start_count = 0
|
||||
exit_count = 0
|
||||
maze = Maze(w, h)
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
for x, ch in enumerate(line):
|
||||
if ch == '#':
|
||||
maze.set_cell(x, y, 'wall')
|
||||
elif ch == 'S':
|
||||
maze.set_cell(x, y, 'start')
|
||||
start_count += 1
|
||||
elif ch == 'E':
|
||||
maze.set_cell(x, y, 'exit')
|
||||
exit_count += 1
|
||||
else:
|
||||
maze.set_cell(x, y, 'path')
|
||||
|
||||
if start_count != 1 or exit_count != 1:
|
||||
raise ValueError(f"Maze must have one S and one E. Found: S={start_count}, E={exit_count}")
|
||||
|
||||
return maze
|
||||
|
||||
|
||||
class BFS:
|
||||
def __init__(self):
|
||||
self._visited = 0
|
||||
|
||||
def find(self, maze, start, goal):
|
||||
from collections import deque
|
||||
queue = deque([start])
|
||||
parent = {start: None}
|
||||
visited = {start}
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
|
||||
if current == goal:
|
||||
self._visited = len(visited)
|
||||
return self._reconstruct(parent, start, goal)
|
||||
|
||||
for neighbor in maze.neighbours(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
parent[neighbor] = current
|
||||
queue.append(neighbor)
|
||||
|
||||
self._visited = len(visited)
|
||||
return []
|
||||
|
||||
def _reconstruct(self, parent, start, goal):
|
||||
path = []
|
||||
current = goal
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
path.reverse()
|
||||
return path if path and path[0] == start else []
|
||||
|
||||
@property
|
||||
def visited_count(self):
|
||||
return self._visited
|
||||
|
||||
|
||||
class DFS:
|
||||
def __init__(self):
|
||||
self._visited = 0
|
||||
|
||||
def find(self, maze, start, goal):
|
||||
stack = [start]
|
||||
parent = {start: None}
|
||||
visited = {start}
|
||||
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
|
||||
if current == goal:
|
||||
self._visited = len(visited)
|
||||
return self._reconstruct(parent, start, goal)
|
||||
|
||||
for neighbor in maze.neighbours(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
parent[neighbor] = current
|
||||
stack.append(neighbor)
|
||||
|
||||
self._visited = len(visited)
|
||||
return []
|
||||
|
||||
def _reconstruct(self, parent, start, goal):
|
||||
path = []
|
||||
current = goal
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
path.reverse()
|
||||
return path if path and path[0] == start else []
|
||||
|
||||
@property
|
||||
def visited_count(self):
|
||||
return self._visited
|
||||
|
||||
|
||||
class AStar:
|
||||
def __init__(self):
|
||||
self._visited = 0
|
||||
|
||||
def _heuristic(self, cell, goal):
|
||||
return abs(cell.x - goal.x) + abs(cell.y - goal.y)
|
||||
|
||||
def find(self, maze, start, goal):
|
||||
import heapq
|
||||
heap = []
|
||||
counter = 0
|
||||
start_f = self._heuristic(start, goal)
|
||||
heapq.heappush(heap, (start_f, counter, start))
|
||||
counter += 1
|
||||
|
||||
parent = {}
|
||||
g_score = {start: 0}
|
||||
f_score = {start: start_f}
|
||||
visited = set()
|
||||
|
||||
while heap:
|
||||
current_f, _, current = heapq.heappop(heap)
|
||||
visited.add(current)
|
||||
|
||||
if current == goal:
|
||||
self._visited = len(visited)
|
||||
return self._reconstruct(parent, start, goal)
|
||||
|
||||
if current_f > f_score.get(current, float('inf')):
|
||||
continue
|
||||
|
||||
for neighbor in maze.neighbours(current):
|
||||
tentative_g = g_score[current] + 1
|
||||
|
||||
if tentative_g < g_score.get(neighbor, float('inf')):
|
||||
parent[neighbor] = current
|
||||
g_score[neighbor] = tentative_g
|
||||
new_f = tentative_g + self._heuristic(neighbor, goal)
|
||||
f_score[neighbor] = new_f
|
||||
heapq.heappush(heap, (new_f, counter, neighbor))
|
||||
counter += 1
|
||||
|
||||
self._visited = len(visited)
|
||||
return []
|
||||
|
||||
def _reconstruct(self, parent, start, goal):
|
||||
path = []
|
||||
current = goal
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
path.reverse()
|
||||
return path if path and path[0] == start else []
|
||||
|
||||
@property
|
||||
def visited_count(self):
|
||||
return self._visited
|
||||
|
||||
|
||||
class MazeSolver:
|
||||
def __init__(self, maze):
|
||||
self._maze = maze
|
||||
self._algorithm = None
|
||||
|
||||
def set_algorithm(self, algorithm):
|
||||
self._algorithm = algorithm
|
||||
|
||||
def solve(self):
|
||||
if not self._algorithm:
|
||||
raise ValueError("Algorithm not set")
|
||||
|
||||
start_time = time.perf_counter()
|
||||
path = self._algorithm.find(self._maze, self._maze.start, self._maze.exit)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
elapsed_ms = (end_time - start_time) * 1000
|
||||
|
||||
return {
|
||||
'time_ms': elapsed_ms,
|
||||
'visited': self._algorithm.visited_count,
|
||||
'path_length': len(path),
|
||||
'path': path
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DATA_PATH = r"C:\Users\Kirill\2026-rff_mp\fomichevks\docs\data"
|
||||
|
||||
|
||||
class ExperimentRunner:
|
||||
def __init__(self):
|
||||
self.algorithms = {
|
||||
"BFS": BFS(),
|
||||
"DFS": DFS(),
|
||||
"A*": AStar()
|
||||
}
|
||||
self.loader = TextMazeLoader()
|
||||
|
||||
def run_benchmark(self, maze_file: str, algorithm: str, runs: int = 5):
|
||||
try:
|
||||
maze = self.loader.load(maze_file)
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
total_time = 0.0
|
||||
total_visited = 0
|
||||
total_length = 0
|
||||
successes = 0
|
||||
|
||||
for _ in range(runs):
|
||||
solver = MazeSolver(maze)
|
||||
solver.set_algorithm(self.algorithms[algorithm])
|
||||
result = solver.solve()
|
||||
|
||||
if result and result['path_length'] > 0:
|
||||
total_time += result['time_ms']
|
||||
total_visited += result['visited']
|
||||
total_length += result['path_length']
|
||||
successes += 1
|
||||
|
||||
if successes == 0:
|
||||
return None
|
||||
|
||||
return {
|
||||
'time_ms': total_time / successes,
|
||||
'visited_cells': total_visited / successes,
|
||||
'path_length': total_length / successes,
|
||||
'success_rate': successes / runs
|
||||
}
|
||||
|
||||
def run_all_experiments(self, runs: int = 5):
|
||||
mazes_list = [
|
||||
(os.path.join(DATA_PATH, "small.txt"), "Small (10x10)"),
|
||||
(os.path.join(DATA_PATH, "medium.txt"), "Medium (50x50)"),
|
||||
(os.path.join(DATA_PATH, "large.txt"), "Large (100x100)"),
|
||||
(os.path.join(DATA_PATH, "empty.txt"), "Empty"),
|
||||
(os.path.join(DATA_PATH, "no_exit.txt"), "No exit")
|
||||
]
|
||||
|
||||
results = []
|
||||
|
||||
|
||||
print("running experiments")
|
||||
|
||||
print(f"Data path: {DATA_PATH}")
|
||||
|
||||
|
||||
for maze_file, maze_name in mazes_list:
|
||||
if not os.path.exists(maze_file):
|
||||
print(f"\n[warn] File not found: {maze_file}")
|
||||
continue
|
||||
|
||||
print(f"\nTesting: {maze_name}")
|
||||
|
||||
for algo_name in self.algorithms.keys():
|
||||
stats = self.run_benchmark(maze_file, algo_name, runs)
|
||||
|
||||
if stats:
|
||||
print(
|
||||
f" {algo_name}: time={stats['time_ms']:.3f}ms, visited={stats['visited_cells']:.0f}, length={stats['path_length']:.0f}")
|
||||
results.append({
|
||||
'maze': maze_name,
|
||||
'strategy': algo_name,
|
||||
'time_ms': stats['time_ms'],
|
||||
'visited_cells': stats['visited_cells'],
|
||||
'path_length': stats['path_length'],
|
||||
'success_rate': stats['success_rate']
|
||||
})
|
||||
else:
|
||||
print(f" {algo_name}: no path found")
|
||||
results.append({
|
||||
'maze': maze_name,
|
||||
'strategy': algo_name,
|
||||
'time_ms': -1,
|
||||
'visited_cells': -1,
|
||||
'path_length': -1,
|
||||
'success_rate': 0
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def create_visualizations(results):
|
||||
valid_results = [r for r in results if r['time_ms'] > 0]
|
||||
if not valid_results:
|
||||
print("no valid results for visualization")
|
||||
return
|
||||
|
||||
mazes = sorted(set(r['maze'] for r in valid_results))
|
||||
algorithms = ['BFS', 'DFS', 'A*']
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||
fig.suptitle('pathfinding algorithms comparison', fontsize=14)
|
||||
|
||||
x = np.arange(len(mazes))
|
||||
width = 0.25
|
||||
|
||||
# Time chart
|
||||
for i, algo in enumerate(algorithms):
|
||||
times = []
|
||||
for maze in mazes:
|
||||
val = next((r['time_ms'] for r in valid_results
|
||||
if r['maze'] == maze and r['strategy'] == algo), 0)
|
||||
times.append(val)
|
||||
bars = axes[0].bar(x + i * width, times, width, label=algo, alpha=0.8)
|
||||
for bar, val in zip(bars, times):
|
||||
if val > 0:
|
||||
axes[0].text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.5,
|
||||
f'{val:.1f}', ha='center', va='bottom', fontsize=7)
|
||||
|
||||
axes[0].set_title('execution Time (ms)')
|
||||
axes[0].set_ylabel('time (ms)')
|
||||
axes[0].set_xticks(x + width)
|
||||
axes[0].set_xticklabels(mazes, rotation=45, ha='right', fontsize=8)
|
||||
axes[0].legend()
|
||||
axes[0].grid(alpha=0.3, axis='y')
|
||||
|
||||
# Visited cells chart
|
||||
for i, algo in enumerate(algorithms):
|
||||
visited = []
|
||||
for maze in mazes:
|
||||
val = next((r['visited_cells'] for r in valid_results
|
||||
if r['maze'] == maze and r['strategy'] == algo), 0)
|
||||
visited.append(val)
|
||||
bars = axes[1].bar(x + i * width, visited, width, label=algo, alpha=0.8)
|
||||
for bar, val in zip(bars, visited):
|
||||
if val > 0:
|
||||
axes[1].text(bar.get_x() + bar.get_width() / 2, bar.get_height(),
|
||||
f'{val:.0f}', ha='center', va='bottom', fontsize=7)
|
||||
|
||||
axes[1].set_title('visited Cells')
|
||||
axes[1].set_ylabel('count')
|
||||
axes[1].set_xticks(x + width)
|
||||
axes[1].set_xticklabels(mazes, rotation=45, ha='right', fontsize=8)
|
||||
axes[1].legend()
|
||||
axes[1].grid(alpha=0.3, axis='y')
|
||||
|
||||
# Path length chart
|
||||
for i, algo in enumerate(algorithms):
|
||||
lengths = []
|
||||
for maze in mazes:
|
||||
val = next((r['path_length'] for r in valid_results
|
||||
if r['maze'] == maze and r['strategy'] == algo), 0)
|
||||
lengths.append(val)
|
||||
bars = axes[2].bar(x + i * width, lengths, width, label=algo, alpha=0.8)
|
||||
for bar, val in zip(bars, lengths):
|
||||
if val > 0:
|
||||
axes[2].text(bar.get_x() + bar.get_width() / 2, bar.get_height(),
|
||||
f'{val:.0f}', ha='center', va='bottom', fontsize=7)
|
||||
|
||||
axes[2].set_title('path Length')
|
||||
axes[2].set_ylabel('steps')
|
||||
axes[2].set_xticks(x + width)
|
||||
axes[2].set_xticklabels(mazes, rotation=45, ha='right', fontsize=8)
|
||||
axes[2].legend()
|
||||
axes[2].grid(alpha=0.3, axis='y')
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
output_path = os.path.join(DATA_PATH, 'experiment_results.png')
|
||||
plt.savefig(output_path, dpi=150, bbox_inches='tight')
|
||||
print(f"\nPlot saved to: {output_path}")
|
||||
plt.show()
|
||||
|
||||
|
||||
def save_results_to_csv(results, filename='experiment_results.csv'):
|
||||
if not results:
|
||||
return
|
||||
|
||||
filepath = os.path.join(DATA_PATH, filename)
|
||||
with open(filepath, 'w', newline='', encoding='utf-8') as f:
|
||||
fieldnames = ['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length', 'success_rate']
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
writer.writerows(results)
|
||||
|
||||
print(f"Results saved to: {filepath}")
|
||||
|
||||
|
||||
def analyze_efficiency(results):
|
||||
valid_results = [r for r in results if r['time_ms'] > 0]
|
||||
if not valid_results:
|
||||
print("no valid results for analysis")
|
||||
return
|
||||
|
||||
algo_stats = {}
|
||||
for algo in ['BFS', 'DFS', 'A*']:
|
||||
algo_data = [r for r in valid_results if r['strategy'] == algo]
|
||||
if algo_data:
|
||||
algo_stats[algo] = {
|
||||
'avg_time': sum(r['time_ms'] for r in algo_data) / len(algo_data),
|
||||
'avg_visited': sum(r['visited_cells'] for r in algo_data) / len(algo_data),
|
||||
'avg_length': sum(r['path_length'] for r in algo_data) / len(algo_data)
|
||||
}
|
||||
|
||||
|
||||
print("average values across all mazes")
|
||||
print(f"{'Algorithm':<12} {'Time (ms)':<15} {'Visited':<15} {'Path length':<15}")
|
||||
|
||||
for algo, stats in algo_stats.items():
|
||||
print(f"{algo:<12} {stats['avg_time']:<15.3f} {stats['avg_visited']:<15.1f} {stats['avg_length']:<15.1f}")
|
||||
|
||||
fastest = min(algo_stats.items(), key=lambda x: x[1]['avg_time'])
|
||||
optimal = min(algo_stats.items(), key=lambda x: x[1]['avg_length'])
|
||||
efficient = min(algo_stats.items(), key=lambda x: x[1]['avg_visited'])
|
||||
|
||||
print("conclusions:")
|
||||
print(f" fastest algorithm: {fastest[0]} ({fastest[1]['avg_time']:.3f} ms avg)")
|
||||
print(f" optimal path: {optimal[0]} ({optimal[1]['avg_length']:.1f} steps avg)")
|
||||
print(f" most efficient (fewest visits): {efficient[0]} ({efficient[1]['avg_visited']:.0f} cells avg)")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
|
||||
if not os.path.exists(DATA_PATH):
|
||||
print(f"\nerr: directory not found: {DATA_PATH}")
|
||||
print("please create the directory and place maze files there.")
|
||||
print("\nexpected structure:")
|
||||
print(f" {DATA_PATH}/")
|
||||
print(" ├── small.txt")
|
||||
print(" ├── medium.txt")
|
||||
print(" ├── large.txt")
|
||||
print(" ├── empty.txt")
|
||||
print(" └── no_exit.txt")
|
||||
return
|
||||
|
||||
runner = ExperimentRunner()
|
||||
results = runner.run_all_experiments(runs=5)
|
||||
|
||||
if not results:
|
||||
print("\nNo results. Check if maze files exist in:", DATA_PATH)
|
||||
return
|
||||
|
||||
save_results_to_csv(results)
|
||||
analyze_efficiency(results)
|
||||
create_visualizations(results)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
fomichevks/docs/data/small.txt
Normal file
10
fomichevks/docs/data/small.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
### #####
|
||||
# # E#
|
||||
# # # # ##
|
||||
# # #
|
||||
####### #
|
||||
# #
|
||||
# ###### #
|
||||
##########
|
||||
BIN
fomichevks/docs/performance_comparison.png
Normal file
BIN
fomichevks/docs/performance_comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
109
fomichevks/docs/results.csv
Normal file
109
fomichevks/docs/results.csv
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
Структура,Режим,Операция,Время (сек)
|
||||
LinkedList,случайный,insert,3.450394299999971
|
||||
LinkedList,случайный,find,0.02368320000005042
|
||||
LinkedList,случайный,delete,0.0178195999997115
|
||||
HashTable,случайный,insert,0.009212800000568677
|
||||
HashTable,случайный,find,6.169999960548012e-05
|
||||
HashTable,случайный,delete,3.860000015265541e-05
|
||||
BST,случайный,insert,0.015902500000265718
|
||||
BST,случайный,find,0.00014120000014372636
|
||||
BST,случайный,delete,8.770000022195745e-05
|
||||
LinkedList,случайный,insert,3.517222700000275
|
||||
LinkedList,случайный,find,0.026108199999725912
|
||||
LinkedList,случайный,delete,0.01791400000001886
|
||||
HashTable,случайный,insert,0.009205899999869871
|
||||
HashTable,случайный,find,5.869999949936755e-05
|
||||
HashTable,случайный,delete,3.609999930631602e-05
|
||||
BST,случайный,insert,0.017175400000269292
|
||||
BST,случайный,find,0.0001505999998698826
|
||||
BST,случайный,delete,9.200000022246968e-05
|
||||
LinkedList,случайный,insert,3.435324000000037
|
||||
LinkedList,случайный,find,0.026613500000166823
|
||||
LinkedList,случайный,delete,0.020348300000478048
|
||||
HashTable,случайный,insert,0.01095050000003539
|
||||
HashTable,случайный,find,6.169999960548012e-05
|
||||
HashTable,случайный,delete,3.9000000469968654e-05
|
||||
BST,случайный,insert,0.015578700000332901
|
||||
BST,случайный,find,0.0001768000001902692
|
||||
BST,случайный,delete,0.00010370000018156134
|
||||
LinkedList,случайный,insert,3.4476645999993707
|
||||
LinkedList,случайный,find,0.025092200000472076
|
||||
LinkedList,случайный,delete,0.017970900000364054
|
||||
HashTable,случайный,insert,0.008620399999927031
|
||||
HashTable,случайный,find,5.6599999879836105e-05
|
||||
HashTable,случайный,delete,3.699999979289714e-05
|
||||
BST,случайный,insert,0.016901099999813596
|
||||
BST,случайный,find,0.0001353999996354105
|
||||
BST,случайный,delete,8.39000003907131e-05
|
||||
LinkedList,случайный,insert,3.4527680000001055
|
||||
LinkedList,случайный,find,0.02482880000025034
|
||||
LinkedList,случайный,delete,0.01792089999980817
|
||||
HashTable,случайный,insert,0.00791659999958938
|
||||
HashTable,случайный,find,0.00012760000026901253
|
||||
HashTable,случайный,delete,6.010000015521655e-05
|
||||
BST,случайный,insert,0.01643800000056217
|
||||
BST,случайный,find,0.0001905999997688923
|
||||
BST,случайный,delete,9.900000077323057e-05
|
||||
LinkedList,отсортированный,insert,3.2146765999996205
|
||||
LinkedList,отсортированный,find,0.02251799999976356
|
||||
LinkedList,отсортированный,delete,0.016432399999757763
|
||||
HashTable,отсортированный,insert,0.008092500000202563
|
||||
HashTable,отсортированный,find,7.089999962772708e-05
|
||||
HashTable,отсортированный,delete,4.069999977218686e-05
|
||||
BST,отсортированный,insert,8.144065100000262
|
||||
BST,отсортированный,find,0.07145860000036919
|
||||
BST,отсортированный,delete,0.041536599999744794
|
||||
LinkedList,отсортированный,insert,3.2909168000005593
|
||||
LinkedList,отсортированный,find,0.1718697999995129
|
||||
LinkedList,отсортированный,delete,0.03186750000077154
|
||||
HashTable,отсортированный,insert,0.014283700000305544
|
||||
HashTable,отсортированный,find,9.820000013860408e-05
|
||||
HashTable,отсортированный,delete,5.8200000239594374e-05
|
||||
BST,отсортированный,insert,7.79496620000009
|
||||
BST,отсортированный,find,0.06252070000027743
|
||||
BST,отсортированный,delete,0.04316579999976966
|
||||
LinkedList,отсортированный,insert,3.3210246999997253
|
||||
LinkedList,отсортированный,find,0.020591699999386037
|
||||
LinkedList,отсортированный,delete,0.016228899999987334
|
||||
HashTable,отсортированный,insert,0.007315800000469608
|
||||
HashTable,отсортированный,find,5.450000026030466e-05
|
||||
HashTable,отсортированный,delete,3.370000013092067e-05
|
||||
BST,отсортированный,insert,8.219712999999501
|
||||
BST,отсортированный,find,0.0645872999994026
|
||||
BST,отсортированный,delete,0.04166759999952774
|
||||
LinkedList,отсортированный,insert,3.3059798000003866
|
||||
LinkedList,отсортированный,find,0.020161800000096264
|
||||
LinkedList,отсортированный,delete,0.016405999999733467
|
||||
HashTable,отсортированный,insert,0.008103499999378982
|
||||
HashTable,отсортированный,find,6.690000009257346e-05
|
||||
HashTable,отсортированный,delete,3.999999989900971e-05
|
||||
BST,отсортированный,insert,9.020431099999769
|
||||
BST,отсортированный,find,0.06939630000033503
|
||||
BST,отсортированный,delete,0.04487580000022717
|
||||
LinkedList,отсортированный,insert,3.5286267000001317
|
||||
LinkedList,отсортированный,find,0.022289700000328594
|
||||
LinkedList,отсортированный,delete,0.018663600000763836
|
||||
HashTable,отсортированный,insert,0.010729900000114867
|
||||
HashTable,отсортированный,find,7.849999929021578e-05
|
||||
HashTable,отсортированный,delete,4.8600000809528865e-05
|
||||
BST,отсортированный,insert,8.329646700000012
|
||||
BST,отсортированный,find,0.06335099999978411
|
||||
BST,отсортированный,delete,0.042559800000162795
|
||||
LinkedList,случайный,insert (СРЕДНЕЕ),3.4606747199999517
|
||||
LinkedList,случайный,find (СРЕДНЕЕ),0.025265180000133114
|
||||
LinkedList,случайный,delete (СРЕДНЕЕ),0.018394740000076126
|
||||
LinkedList,отсортированный,insert (СРЕДНЕЕ),3.3322449200000848
|
||||
LinkedList,отсортированный,find (СРЕДНЕЕ),0.051486199999817475
|
||||
LinkedList,отсортированный,delete (СРЕДНЕЕ),0.019919680000202788
|
||||
HashTable,случайный,insert (СРЕДНЕЕ),0.00918123999999807
|
||||
HashTable,случайный,find (СРЕДНЕЕ),7.325999977183528e-05
|
||||
HashTable,случайный,delete (СРЕДНЕЕ),4.215999997541076e-05
|
||||
HashTable,отсортированный,insert (СРЕДНЕЕ),0.009705080000094313
|
||||
HashTable,отсортированный,find (СРЕДНЕЕ),7.379999988188501e-05
|
||||
HashTable,отсортированный,delete (СРЕДНЕЕ),4.4240000170248096e-05
|
||||
BST,случайный,insert (СРЕДНЕЕ),0.016399140000248735
|
||||
BST,случайный,find (СРЕДНЕЕ),0.0001589199999216362
|
||||
BST,случайный,delete (СРЕДНЕЕ),9.326000035798643e-05
|
||||
BST,отсортированный,insert (СРЕДНЕЕ),8.301764419999927
|
||||
BST,отсортированный,find (СРЕДНЕЕ),0.06626278000003367
|
||||
BST,отсортированный,delete (СРЕДНЕЕ),0.04276111999988643
|
||||
|
137
fomichevks/docs/отчет.txt
Normal file
137
fomichevks/docs/отчет.txt
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
1. Цель работы
|
||||
Реализовать три базовые структуры данных без использования объектно-ориентированных механизмов, применить их для хранения записей телефонного справочника, экспериментально измерить производительность операций вставки, поиска и удаления, а также проанализировать влияние порядка входных данных на время выполнения.
|
||||
|
||||
Связный список (LinkedListPhoneBook)
|
||||
Узел: {'name': str, 'phone': str, 'next': dict | None}
|
||||
Операции:
|
||||
ll_insert: линейный проход до конца, обновление при совпадении имени, вставка нового узла в хвост. Возвращает голову списка.
|
||||
ll_find: последовательный перебор до первого совпадения.
|
||||
ll_delete: поиск предшественника удаляемого узла, переназначение ссылки next.
|
||||
ll_list_all: сбор записей в список, явная сортировка по имени.
|
||||
|
||||
Хеш-таблица с цепочками (HashTable)
|
||||
Структура: список из BUCKET_COUNT = 1024 элементов, каждый элемент — голова связного списка.
|
||||
Хеширование: idx = hash(name) % BUCKET_COUNT
|
||||
Операции: делегируют соответствующим ll_* функциям для конкретного бакета.
|
||||
|
||||
Узел: {'name': str, 'phone': str, 'left': dict | None, 'right': dict | None}
|
||||
Операции:
|
||||
bst_insert: рекурсивное сравнение имён, создание листа при достижении None.
|
||||
bst_find: рекурсивный спуск влево/вправо в зависимости от результата сравнения.
|
||||
bst_delete: три случая: 0 потомков, 1 потомок, 2 потомка. При двух потомках используется inorder-преемник (минимальный элемент правого поддерева).
|
||||
bst_list_all: центрированный (in-order) обход, гарантирующий отсортированный вывод без дополнительной сортировки.
|
||||
|
||||
Влияние порядка входных данных на скорость вставки в BST
|
||||
Двоичное дерево поиска (BST) поддерживает инвариант: left.name < root.name < right.name. При вставке новых узлов алгоритм рекурсивно спускается по дереву, выбирая левую или правую ветвь в зависимости от результата сравнения.
|
||||
|
||||
Случай 1: Случайный порядок данных
|
||||
Ключи распределяются по дереву хаотично
|
||||
Левые и правые поддеревья заполняются примерно равномерно
|
||||
Высота дерева: h ≈ log₂(N) ≈ 14 для N=10 000
|
||||
Сложность вставки одного элемента: O(log N)
|
||||
Общая сложность вставки всех N элементов: O(N log N)
|
||||
|
||||
Случай 2: Отсортированный порядок данных
|
||||
Каждый следующий ключ больше всех предыдущих
|
||||
Алгоритм всегда выбирает правую ветвь
|
||||
Дерево вырождается в линейную цепочку
|
||||
|
||||
Почему хеш-таблица почти не чувствительна к порядку?
|
||||
|
||||
Функция hash() в Python:
|
||||
Детерминирована: один и тот же ключ → один и тот же хеш
|
||||
Равномерно распределяет значения по пространству хешей
|
||||
Не зависит от порядка вызова: hash("User_00001") всегда одинаков
|
||||
|
||||
Распределение по бакетам
|
||||
При N=10 000 записей и 1024 бакетах:
|
||||
Ожидаемая загрузка: α = N / BUCKET_COUNT ≈ 9.77 элементов на бакет
|
||||
Даже если все ключи отсортированы, их хеши «размазываются» по всему диапазону
|
||||
Внутри каждого бакета хранится короткий связный список (~10 элементов)
|
||||
|
||||
Почему связный список всегда медленен при поиске?
|
||||
|
||||
Связный список хранит элементы последовательно, без индексации
|
||||
Для поиска элемента с именем X:
|
||||
Начать с головы списка
|
||||
Сравнить curr['name'] == X
|
||||
Если не совпало → перейти к curr['next']
|
||||
Повторять до нахождения или конца списка
|
||||
Связный список не подходит для задач с частым поиском. Его удел очереди, стеки, или вспомогательная роль внутри других структур.
|
||||
|
||||
Как удаление работает в каждой структуре?
|
||||
Связный список
|
||||
def ll_delete(head, name):
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
curr = head
|
||||
while curr['next']:
|
||||
if curr['next']['name'] == name:
|
||||
curr['next'] = curr['next']['next']
|
||||
return head
|
||||
curr = curr['next']
|
||||
return head
|
||||
|
||||
Поиск узла (или его предшественника) — O(N)
|
||||
Переназначение ссылки next — O(1)
|
||||
Сборка мусора (автоматически в Python)
|
||||
|
||||
Хеш-таблица
|
||||
def ht_delete(buckets, name):
|
||||
idx = hash(name) % BUCKET_COUNT
|
||||
buckets[idx] = ll_delete(buckets[idx], name)
|
||||
|
||||
Вычисление индекса бакета — O(1)
|
||||
Поиск и удаление в связном списке бакета — O(L), где L ≈ 10
|
||||
Итого: O(1) в среднем
|
||||
|
||||
Двоичное дерево поиска
|
||||
def bst_delete(root, name):
|
||||
# 1. Поиск узла
|
||||
if name < root['name']:
|
||||
root['left'] = bst_delete(root['left'], name)
|
||||
elif name > root['name']:
|
||||
root['right'] = bst_delete(root['right'], name)
|
||||
else:
|
||||
# 2. Три случая удаления
|
||||
if root['left'] is None:
|
||||
return root['right'] # 0 или 1 потомок
|
||||
elif root['right'] is None:
|
||||
return root['left']
|
||||
else:
|
||||
# 2 потомка: найти inorder-преемника
|
||||
successor = _bst_find_min(root['right'])
|
||||
root['name'] = successor['name']
|
||||
root['phone'] = successor['phone']
|
||||
root['right'] = bst_delete(root['right'], successor['name'])
|
||||
return root
|
||||
|
||||
Поиск удаляемого узла — O(h)
|
||||
Обработка случая:
|
||||
0 потомков: просто удалить узел
|
||||
1 потомок: «поднять» потомка на место удаляемого
|
||||
2 потомка: найти минимум в правом поддереве (inorder-преемник), скопировать его данные, рекурсивно удалить преемника
|
||||
Возврат обновлённого корня поддерева
|
||||
|
||||
Когда какую структуру использовать?
|
||||
|
||||
| Сценарий | Рекомендация |
|
||||
|---|---|
|
||||
| **Частый поиск** по имени | HashTable или BST (случайные данные) |
|
||||
| **Данные приходят отсортированными** | HashTable (BST деградирует!) |
|
||||
| **Нужен отсортированный список** | BST (in-order обход — бесплатный) |
|
||||
| **Частые вставки/удаления + поиск** | HashTable |
|
||||
| **Минимальная память, простота** | LinkedList (для малых N) |
|
||||
| **Диапазонные запросы** (все имена A–M) | BST |
|
||||
|
||||
### Сложности операций
|
||||
|
||||
| Структура | Insert | Find | Delete | List (sorted) |
|
||||
|---|---|---|---|---|
|
||||
| LinkedList | O(n) | O(n) | O(n) | O(n log n) |
|
||||
| HashTable | O(1) avg | O(1) avg | O(1) avg | O(n log n) |
|
||||
| BST (сбалансированный) | O(log n) | O(log n) | O(log n) | O(n) |
|
||||
| BST (вырожденный) | O(n) | O(n) | O(n) | O(n) |
|
||||
|
||||
|
||||
HashTable — лучший выбор для телефонного справочника при частых вставках и поисках. BST лучше HashTable только если нужен отсортированный вывод без дополнительной сортировки — но при условии случайного порядка вставки или использования самобалансирующегося дерева (AVL, Red-Black).
|
||||
257
fomichevks/docs/структуры_данных.py
Normal file
257
fomichevks/docs/структуры_данных.py
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
import random
|
||||
import time
|
||||
import csv
|
||||
import sys
|
||||
|
||||
|
||||
sys.setrecursionlimit(20000)
|
||||
|
||||
|
||||
# 1. СВЯЗНЫЙ СПИСОК
|
||||
def ll_insert(head, name, phone):
|
||||
curr = head
|
||||
while curr:
|
||||
if curr['name'] == name:
|
||||
curr['phone'] = phone
|
||||
return head
|
||||
curr = curr['next']
|
||||
|
||||
new_node = {'name': name, 'phone': phone, 'next': None}
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
curr = head
|
||||
while curr['next']:
|
||||
curr = curr['next']
|
||||
curr['next'] = new_node
|
||||
return head
|
||||
|
||||
|
||||
def ll_find(head, name):
|
||||
curr = head
|
||||
while curr:
|
||||
if curr['name'] == name:
|
||||
return curr['phone']
|
||||
curr = curr['next']
|
||||
return None
|
||||
|
||||
|
||||
def ll_delete(head, name):
|
||||
if head is None:
|
||||
return None
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
curr = head
|
||||
while curr['next']:
|
||||
if curr['next']['name'] == name:
|
||||
curr['next'] = curr['next']['next']
|
||||
return head
|
||||
curr = curr['next']
|
||||
return head
|
||||
|
||||
|
||||
def ll_list_all(head):
|
||||
res = []
|
||||
curr = head
|
||||
while curr:
|
||||
res.append((curr['name'], curr['phone']))
|
||||
curr = curr['next']
|
||||
res.sort(key=lambda x: x[0])
|
||||
return res
|
||||
|
||||
|
||||
|
||||
# 2. ХЕШ-ТАБЛИЦА
|
||||
BUCKET_COUNT = 1024
|
||||
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
idx = hash(name) % BUCKET_COUNT
|
||||
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
||||
|
||||
|
||||
def ht_find(buckets, name):
|
||||
idx = hash(name) % BUCKET_COUNT
|
||||
return ll_find(buckets[idx], name)
|
||||
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
idx = hash(name) % BUCKET_COUNT
|
||||
buckets[idx] = ll_delete(buckets[idx], name)
|
||||
|
||||
|
||||
def ht_list_all(buckets):
|
||||
res = []
|
||||
for head in buckets:
|
||||
curr = head
|
||||
while curr:
|
||||
res.append((curr['name'], curr['phone']))
|
||||
curr = curr['next']
|
||||
res.sort(key=lambda x: x[0])
|
||||
return res
|
||||
|
||||
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
if root is None:
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
if name < root['name']:
|
||||
root['left'] = bst_insert(root['left'], name, phone)
|
||||
elif name > root['name']:
|
||||
root['right'] = bst_insert(root['right'], name, phone)
|
||||
else:
|
||||
root['phone'] = 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_find_min(node):
|
||||
curr = node
|
||||
while curr['left'] is not None:
|
||||
curr = curr['left']
|
||||
return curr
|
||||
|
||||
|
||||
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']
|
||||
else:
|
||||
successor = _bst_find_min(root['right'])
|
||||
root['name'] = successor['name']
|
||||
root['phone'] = successor['phone']
|
||||
root['right'] = bst_delete(root['right'], successor['name'])
|
||||
return root
|
||||
|
||||
|
||||
def bst_list_all(root):
|
||||
res = []
|
||||
|
||||
def inorder(node):
|
||||
if node:
|
||||
inorder(node['left'])
|
||||
res.append((node['name'], node['phone']))
|
||||
inorder(node['right'])
|
||||
|
||||
inorder(root)
|
||||
return res
|
||||
|
||||
|
||||
|
||||
# ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ
|
||||
def run_experiments():
|
||||
N = 10000
|
||||
base_records = [(f"User_{i:05d}", f"100{i:05d}") for i in range(N)]
|
||||
|
||||
records_sorted = sorted(base_records, key=lambda x: x[0])
|
||||
records_shuffled = base_records[:]
|
||||
random.shuffle(records_shuffled)
|
||||
|
||||
all_names = [r[0] for r in base_records]
|
||||
find_existing = random.sample(all_names, 100)
|
||||
find_non_existing = [f"Missing_{i}" for i in range(10)]
|
||||
delete_targets = random.sample(all_names, 50)
|
||||
|
||||
all_results = []
|
||||
structures = ["LinkedList", "HashTable", "BST"]
|
||||
data_modes = [("случайный", records_shuffled), ("отсортированный", records_sorted)]
|
||||
|
||||
for mode_name, records in data_modes:
|
||||
print(f"\n Режим: {mode_name}")
|
||||
for run in range(1, 6):
|
||||
print(f" запуск {run}/5")
|
||||
|
||||
|
||||
head = None
|
||||
t = time.perf_counter()
|
||||
for n, p in records: head = ll_insert(head, n, p)
|
||||
t_ins = time.perf_counter() - t
|
||||
|
||||
t = time.perf_counter()
|
||||
for n in find_existing + find_non_existing: ll_find(head, n)
|
||||
t_find = time.perf_counter() - t
|
||||
|
||||
t = time.perf_counter()
|
||||
for n in delete_targets: head = ll_delete(head, n)
|
||||
t_del = time.perf_counter() - t
|
||||
|
||||
all_results.append(["LinkedList", mode_name, "insert", t_ins])
|
||||
all_results.append(["LinkedList", mode_name, "find", t_find])
|
||||
all_results.append(["LinkedList", mode_name, "delete", t_del])
|
||||
|
||||
|
||||
buckets = [None] * BUCKET_COUNT
|
||||
t = time.perf_counter()
|
||||
for n, p in records: ht_insert(buckets, n, p)
|
||||
t_ins = time.perf_counter() - t
|
||||
|
||||
t = time.perf_counter()
|
||||
for n in find_existing + find_non_existing: ht_find(buckets, n)
|
||||
t_find = time.perf_counter() - t
|
||||
|
||||
t = time.perf_counter()
|
||||
for n in delete_targets: ht_delete(buckets, n)
|
||||
t_del = time.perf_counter() - t
|
||||
|
||||
all_results.append(["HashTable", mode_name, "insert", t_ins])
|
||||
all_results.append(["HashTable", mode_name, "find", t_find])
|
||||
all_results.append(["HashTable", mode_name, "delete", t_del])
|
||||
|
||||
|
||||
root = None
|
||||
t = time.perf_counter()
|
||||
for n, p in records: root = bst_insert(root, n, p)
|
||||
t_ins = time.perf_counter() - t
|
||||
|
||||
t = time.perf_counter()
|
||||
for n in find_existing + find_non_existing: bst_find(root, n)
|
||||
t_find = time.perf_counter() - t
|
||||
|
||||
t = time.perf_counter()
|
||||
for n in delete_targets: root = bst_delete(root, n)
|
||||
t_del = time.perf_counter() - t
|
||||
|
||||
all_results.append(["BST", mode_name, "insert", t_ins])
|
||||
all_results.append(["BST", mode_name, "find", t_find])
|
||||
all_results.append(["BST", mode_name, "delete", t_del])
|
||||
|
||||
|
||||
averages = []
|
||||
for struct in structures:
|
||||
for mode in ["случайный", "отсортированный"]:
|
||||
for op in ["insert", "find", "delete"]:
|
||||
times = [r[3] for r in all_results if r[0] == struct and r[1] == mode and r[2] == op]
|
||||
avg = sum(times) / len(times)
|
||||
averages.append([struct, mode, f"{op} (СРЕДНЕЕ)", avg])
|
||||
|
||||
final_csv_data = [["Структура", "Режим", "Операция", "Время (сек)"]] + all_results + averages
|
||||
|
||||
with open("results.csv", "w", newline="", encoding="utf-8-sig") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows(final_csv_data)
|
||||
|
||||
return all_results, averages
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raw_data, avg_data = run_experiments()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user