forked from UNN/2026-rff_mp
[1,2] объединил задания в один запрос
This commit is contained in:
commit
81ffeb0def
9
raskatovia/docs/data/task2/maps/hard.txt
Normal file
9
raskatovia/docs/data/task2/maps/hard.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
###############
|
||||
#S....#.......#
|
||||
#.###.#.#####.#
|
||||
#...#.#.....#.#
|
||||
###.#.#####.#.#
|
||||
#...#.....#.#.#
|
||||
#.#######.#.#.#
|
||||
#............F#
|
||||
###############
|
||||
7
raskatovia/docs/data/task2/maps/medium.txt
Normal file
7
raskatovia/docs/data/task2/maps/medium.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
###########
|
||||
#S..#.....#
|
||||
###.#.###.#
|
||||
#...#...#.#
|
||||
#.#####.#.#
|
||||
#.......#F#
|
||||
###########
|
||||
5
raskatovia/docs/data/task2/maps/simple.txt
Normal file
5
raskatovia/docs/data/task2/maps/simple.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#######
|
||||
#S...F#
|
||||
#.###.#
|
||||
#.....#
|
||||
#######
|
||||
96
raskatovia/docs/data/task2/maze.py
Normal file
96
raskatovia/docs/data/task2/maze.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
class Cell:
|
||||
def __init__(self, row, col, value):
|
||||
self.row = row
|
||||
self.col = col
|
||||
self.value = value
|
||||
|
||||
def is_wall(self):
|
||||
return self.value == "#"
|
||||
|
||||
def is_start(self):
|
||||
return self.value == "S"
|
||||
|
||||
def is_finish(self):
|
||||
return self.value == "F"
|
||||
|
||||
|
||||
class Maze:
|
||||
def __init__(self, cells, start, finish):
|
||||
self.cells = cells
|
||||
self.start = start
|
||||
self.finish = finish
|
||||
self.height = len(cells)
|
||||
self.width = len(cells[0]) if cells else 0
|
||||
|
||||
def inside(self, row, col):
|
||||
return 0 <= row < self.height and 0 <= col < self.width
|
||||
|
||||
def get_cell(self, row, col):
|
||||
if not self.inside(row, col):
|
||||
return None
|
||||
return self.cells[row][col]
|
||||
|
||||
def is_free(self, row, col):
|
||||
cell = self.get_cell(row, col)
|
||||
return cell is not None and not cell.is_wall()
|
||||
|
||||
def neighbors(self, row, col):
|
||||
variants = [
|
||||
(row - 1, col),
|
||||
(row + 1, col),
|
||||
(row, col - 1),
|
||||
(row, col + 1)
|
||||
]
|
||||
result = []
|
||||
for next_row, next_col in variants:
|
||||
if self.is_free(next_row, next_col):
|
||||
result.append((next_row, next_col))
|
||||
return result
|
||||
|
||||
def draw(self, path=None):
|
||||
path_set = set(path) if path else set()
|
||||
lines = []
|
||||
for row in range(self.height):
|
||||
line = ""
|
||||
for col in range(self.width):
|
||||
cell = self.cells[row][col]
|
||||
if (row, col) in path_set and not cell.is_start() and not cell.is_finish():
|
||||
line += "*"
|
||||
else:
|
||||
line += cell.value
|
||||
lines.append(line)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class MazeBuilder:
|
||||
def __init__(self):
|
||||
self.lines = []
|
||||
|
||||
def from_file(self, filename):
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
self.lines = [line.rstrip("\n") for line in file if line.strip()]
|
||||
return self
|
||||
|
||||
def build(self):
|
||||
cells = []
|
||||
start = None
|
||||
finish = None
|
||||
width = len(self.lines[0])
|
||||
|
||||
for row, line in enumerate(self.lines):
|
||||
if len(line) != width:
|
||||
raise ValueError("maze lines have different length")
|
||||
cell_row = []
|
||||
for col, value in enumerate(line):
|
||||
cell = Cell(row, col, value)
|
||||
if cell.is_start():
|
||||
start = (row, col)
|
||||
if cell.is_finish():
|
||||
finish = (row, col)
|
||||
cell_row.append(cell)
|
||||
cells.append(cell_row)
|
||||
|
||||
if start is None or finish is None:
|
||||
raise ValueError("maze must have start and finish")
|
||||
|
||||
return Maze(cells, start, finish)
|
||||
46
raskatovia/docs/data/task2/results.csv
Normal file
46
raskatovia/docs/data/task2/results.csv
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
map,algorithm,try,time,visited,length
|
||||
simple.txt,BFS,1,2.6000008801929653e-05,9,5
|
||||
simple.txt,BFS,2,1.4800010831095278e-05,9,5
|
||||
simple.txt,BFS,3,1.2799995602108538e-05,9,5
|
||||
simple.txt,BFS,4,1.1600001016631722e-05,9,5
|
||||
simple.txt,BFS,5,1.1399999493733048e-05,9,5
|
||||
simple.txt,DFS,1,9.60000033956021e-06,5,5
|
||||
simple.txt,DFS,2,7.199996616691351e-06,5,5
|
||||
simple.txt,DFS,3,6.300004315562546e-06,5,5
|
||||
simple.txt,DFS,4,6.200003554113209e-06,5,5
|
||||
simple.txt,DFS,5,6.200003554113209e-06,5,5
|
||||
simple.txt,A*,1,1.4599994756281376e-05,5,5
|
||||
simple.txt,A*,2,9.499999578110874e-06,5,5
|
||||
simple.txt,A*,3,8.29999044071883e-06,5,5
|
||||
simple.txt,A*,4,8.300004992634058e-06,5,5
|
||||
simple.txt,A*,5,2.4499997380189598e-05,5,5
|
||||
medium.txt,BFS,1,4.0400002035312355e-05,29,29
|
||||
medium.txt,BFS,2,3.700000524986535e-05,29,29
|
||||
medium.txt,BFS,3,3.670000296551734e-05,29,29
|
||||
medium.txt,BFS,4,3.470000228844583e-05,29,29
|
||||
medium.txt,BFS,5,3.370000922586769e-05,29,29
|
||||
medium.txt,DFS,1,3.4199998481199145e-05,29,29
|
||||
medium.txt,DFS,2,3.369999467395246e-05,29,29
|
||||
medium.txt,DFS,3,3.329999162815511e-05,29,29
|
||||
medium.txt,DFS,4,3.309999010525644e-05,29,29
|
||||
medium.txt,DFS,5,3.300000389572233e-05,29,29
|
||||
medium.txt,A*,1,4.470000567380339e-05,29,29
|
||||
medium.txt,A*,2,4.549999721348286e-05,29,29
|
||||
medium.txt,A*,3,4.259998968336731e-05,29,29
|
||||
medium.txt,A*,4,4.260000423528254e-05,29,29
|
||||
medium.txt,A*,5,4.1799998143687844e-05,29,29
|
||||
hard.txt,BFS,1,4.680000711232424e-05,38,19
|
||||
hard.txt,BFS,2,4.390001413412392e-05,38,19
|
||||
hard.txt,BFS,3,4.4200001866556704e-05,38,19
|
||||
hard.txt,BFS,4,4.2100000428035855e-05,38,19
|
||||
hard.txt,BFS,5,4.389999958220869e-05,38,19
|
||||
hard.txt,DFS,1,2.570000651758164e-05,19,19
|
||||
hard.txt,DFS,2,2.1800005924887955e-05,19,19
|
||||
hard.txt,DFS,3,2.19999928958714e-05,19,19
|
||||
hard.txt,DFS,4,2.1799991372972727e-05,19,19
|
||||
hard.txt,DFS,5,2.1799991372972727e-05,19,19
|
||||
hard.txt,A*,1,4.149999585933983e-05,25,19
|
||||
hard.txt,A*,2,3.7699996028095484e-05,25,19
|
||||
hard.txt,A*,3,3.6999990697950125e-05,25,19
|
||||
hard.txt,A*,4,3.680000372696668e-05,25,19
|
||||
hard.txt,A*,5,3.720000677276403e-05,25,19
|
||||
|
135
raskatovia/docs/data/task2/solver.py
Normal file
135
raskatovia/docs/data/task2/solver.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
from collections import deque
|
||||
import heapq
|
||||
from maze import MazeBuilder
|
||||
|
||||
from collections import deque
|
||||
from maze import MazeBuilder
|
||||
|
||||
def build_path(previous, start, finish):
|
||||
if finish not in previous:
|
||||
return []
|
||||
path = []
|
||||
current = finish
|
||||
while current != start:
|
||||
path.append(current)
|
||||
current = previous[current]
|
||||
path.append(start)
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
class BfsStrategy:
|
||||
def solve(self, maze):
|
||||
start = maze.start
|
||||
finish = maze.finish
|
||||
queue = deque([start])
|
||||
previous = {start: None}
|
||||
visited_count = 0
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
visited_count += 1
|
||||
|
||||
if current == finish:
|
||||
break
|
||||
|
||||
for next_cell in maze.neighbors(current[0], current[1]):
|
||||
if next_cell not in previous:
|
||||
previous[next_cell] = current
|
||||
queue.append(next_cell)
|
||||
|
||||
path = build_path(previous, start, finish)
|
||||
return {
|
||||
"name": "BFS",
|
||||
"path": path,
|
||||
"visited": visited_count,
|
||||
"length": len(path)
|
||||
}
|
||||
class DfsStrategy:
|
||||
def solve(self, maze):
|
||||
start = maze.start
|
||||
finish = maze.finish
|
||||
stack = [start]
|
||||
previous = {start: None}
|
||||
visited_count = 0
|
||||
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
visited_count += 1
|
||||
|
||||
if current == finish:
|
||||
break
|
||||
|
||||
for next_cell in maze.neighbors(current[0], current[1]):
|
||||
if next_cell not in previous:
|
||||
previous[next_cell] = current
|
||||
stack.append(next_cell)
|
||||
|
||||
path = build_path(previous, start, finish)
|
||||
return {
|
||||
"name": "DFS",
|
||||
"path": path,
|
||||
"visited": visited_count,
|
||||
"length": len(path)
|
||||
}
|
||||
def distance(first, second):
|
||||
return abs(first[0] - second[0]) + abs(first[1] - second[1])
|
||||
|
||||
class AstarStrategy:
|
||||
def solve(self, maze):
|
||||
start = maze.start
|
||||
finish = maze.finish
|
||||
queue = []
|
||||
heapq.heappush(queue, (0, start))
|
||||
previous = {start: None}
|
||||
costs = {start: 0}
|
||||
visited_count = 0
|
||||
|
||||
while queue:
|
||||
current = heapq.heappop(queue)[1]
|
||||
visited_count += 1
|
||||
|
||||
if current == finish:
|
||||
break
|
||||
|
||||
for next_cell in maze.neighbors(current[0], current[1]):
|
||||
new_cost = costs[current] + 1
|
||||
if next_cell not in costs or new_cost < costs[next_cell]:
|
||||
costs[next_cell] = new_cost
|
||||
priority = new_cost + distance(next_cell, finish)
|
||||
heapq.heappush(queue, (priority, next_cell))
|
||||
previous[next_cell] = current
|
||||
|
||||
path = build_path(previous, start, finish)
|
||||
return {
|
||||
"name": "A*",
|
||||
"path": path,
|
||||
"visited": visited_count,
|
||||
"length": len(path)
|
||||
}
|
||||
|
||||
class MazeSolver:
|
||||
def __init__(self, strategy):
|
||||
self.strategy = strategy
|
||||
|
||||
def solve(self, maze):
|
||||
return self.strategy.solve(maze)
|
||||
|
||||
if __name__ == "__main__":
|
||||
files = [
|
||||
"simple.txt",
|
||||
"medium.txt",
|
||||
"hard.txt"
|
||||
]
|
||||
strategies = [BfsStrategy(), DfsStrategy(), AstarStrategy()]
|
||||
|
||||
for filename in files:
|
||||
print("map:", filename)
|
||||
maze = MazeBuilder().from_file("raskatovia/docs/data/task2/maps/" + filename).build()
|
||||
for strategy in strategies:
|
||||
solver = MazeSolver(strategy)
|
||||
result = solver.solve(maze)
|
||||
print("algorithm:", result["name"])
|
||||
print("visited:", result["visited"])
|
||||
print("length:", result["length"])
|
||||
print(maze.draw(result["path"]))
|
||||
print()
|
||||
40
raskatovia/docs/data/task2/zamery.py
Normal file
40
raskatovia/docs/data/task2/zamery.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import csv
|
||||
import time
|
||||
from maze import MazeBuilder
|
||||
from solver import BfsStrategy, DfsStrategy, AstarStrategy, MazeSolver
|
||||
|
||||
MAPS = ["simple.txt", "medium.txt", "hard.txt"]
|
||||
REPEATS = 5
|
||||
|
||||
def run_one(filename, strategy):
|
||||
maze = MazeBuilder().from_file("raskatovia/docs/data/task2/maps/" + filename).build()
|
||||
solver = MazeSolver(strategy)
|
||||
start = time.perf_counter()
|
||||
result = solver.solve(maze)
|
||||
work_time = time.perf_counter() - start
|
||||
return result, work_time
|
||||
|
||||
def main():
|
||||
rows = [["map", "algorithm", "try", "time", "visited", "length"]]
|
||||
strategies = [BfsStrategy(), DfsStrategy(), AstarStrategy()]
|
||||
|
||||
for filename in MAPS:
|
||||
for strategy in strategies:
|
||||
for number in range(1, REPEATS + 1):
|
||||
result, work_time = run_one(filename, strategy)
|
||||
rows.append([
|
||||
filename,
|
||||
result["name"],
|
||||
number,
|
||||
work_time,
|
||||
result["visited"],
|
||||
result["length"]
|
||||
])
|
||||
|
||||
with open("raskatovia/docs/data/task2/results.csv", "w", newline="", encoding="utf-8") as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerows(rows)
|
||||
|
||||
print("results saved")
|
||||
|
||||
main()
|
||||
46
raskatovia/docs/task2report.md
Normal file
46
raskatovia/docs/task2report.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
Цель работы
|
||||
|
||||
В этом задании я сделал программу для поиска пути в лабиринте. Лабиринт загружается из текстового файла, после этого для него запускаются три алгоритма: BFS, DFS и A\*. Нужно было сравнить как они проходят разные карты
|
||||
|
||||
|
||||
|
||||
  В файле maze.py находится описание лабиринта. Cell отвечает за отдельную клетку, а Maze хранит всю карту, старт, финиш и умеет находить соседние клетки, куда можно идти. Для загрузки карты из файла сделан MazeBuilder. Он читает строки, проверяет их длину и ищет точки S и F. \[тут я столкнулся с ошибкой на hard.txt, потому что одна строка была другой длины]
|
||||
|
||||
  В файле solver.py находится поиск пути.MazeSolver получает стратегию поиска и запускает её. Были сделаны три стратегии: BfsStrategy, DfsStrategy и AstarStrategy. BFS идёт в ширину, DFS идёт в глубину, а A\* использует расстояние до финиша
|
||||
|
||||
  Сначала программа проверялась на simple.txt, потом были добавлены medium.txt и hard.txt. Для каждой карты запускались все три алгоритма. Программа выводила найденный путь, количество посещённых клеток и длину пути
|
||||
|
||||
  Для замеров сделан файл zamery.py. Он запускает алгоритмы по 5 раз и сохраняет результаты в results.csv
|
||||
|
||||
  Результаты
|
||||
|
||||
simple.txt; BFS; время 0.00001532; посещено 9; длина пути 5
|
||||
|
||||
simple.txt; DFS; время 0.00000710; посещено 5; длина пути 5
|
||||
|
||||
simple.txt; A\*; время 0.00001304; посещено 5; длина пути 5
|
||||
|
||||
|
||||
|
||||
medium.txt; BFS; время 0.00003650; посещено 29; длина пути 29
|
||||
|
||||
medium.txt; DFS; время 0.00003346; посещено 29; длина пути 29
|
||||
|
||||
medium.txt; A\*; время 0.00004344; посещено 29; длина пути 29
|
||||
|
||||
|
||||
|
||||
hard.txt; BFS; время 0.00004418; посещено 38; длина пути 19
|
||||
|
||||
hard.txt; DFS; время 0.00002262; посещено 19; длина пути 19
|
||||
|
||||
hard.txt; A\*; время 0.00003804; посещено 25; длина пути 19
|
||||
|
||||
|
||||
|
||||
  По результатам видно, что на простом лабиринте разница почти не важна. На medium.txt все алгоритмы прошли примерно одинаково. На hard.txt разница заметнее: BFS посетил больше всего клеток, DFS меньше всего, а A\* оказался между ними. При этом длина пути на hard.txt у всех получилась одинаковая
|
||||
|
||||
  Заключение
|
||||
|
||||
В работе получилось сделать загрузку лабиринта из файла и несколько способов поиска пути. BFS надёжный, но может обходить больше клеток. DFS простой и иногда быстро доходит до финиша, но зависит от формы лабиринта. A\* старается идти ближе к цели, но на маленьких картах его преимущество не всегда видно.В целом задание было понятнее когда появились карты и путь стал выводиться прямо в консоли. Самая заметная проблема была с неправильной строкой в hard.txt, но после исправления все карты начали нормально запускаться
|
||||
|
||||
Loading…
Reference in New Issue
Block a user