diff --git a/rybakovaa/428b.md b/rybakovaa/428b.md new file mode 100644 index 0000000..225a97e --- /dev/null +++ b/rybakovaa/428b.md @@ -0,0 +1 @@ +428b diff --git a/rybakovaa/lab1/docs/data/lab1.py b/rybakovaa/lab1/docs/data/lab1.py new file mode 100644 index 0000000..56c11e1 --- /dev/null +++ b/rybakovaa/lab1/docs/data/lab1.py @@ -0,0 +1,327 @@ +import time +import random +import csv +import os +import sys + +sys.setrecursionlimit(20000) + +BASE = os.path.dirname(os.path.abspath(__file__)) +DATA_PATH = BASE + +N = 10000 +REPEAT = 5 + + +def ll_insert(head, name, phone): + new_node = {"name": name, "phone": phone, "next": head} + return new_node + + +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): + result = [] + curr = head + while curr: + result.append((curr["name"], curr["phone"])) + curr = curr["next"] + result.sort() + return result + + +BUCKET_SIZE = 1000 + + +def ht_insert(buckets, name, phone): + idx = hash(name) % len(buckets) + buckets[idx] = ll_insert(buckets[idx], name, phone) + + +def ht_find(buckets, name): + idx = hash(name) % len(buckets) + return ll_find(buckets[idx], name) + + +def ht_delete(buckets, name): + idx = hash(name) % len(buckets) + buckets[idx] = ll_delete(buckets[idx], name) + + +def ht_list_all(buckets): + result = [] + for bucket in buckets: + curr = bucket + while curr: + result.append((curr["name"], curr["phone"])) + curr = curr["next"] + result.sort() + return result + + +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"] + if name < root["name"]: + return bst_find(root["left"], name) + return bst_find(root["right"], name) + + +def bst_delete(root, name): + if root is None: + return None + if name < root["name"]: + root["left"] = bst_delete(root["left"], name) + elif name > root["name"]: + root["right"] = bst_delete(root["right"], name) + else: + if root["left"] is None: + return root["right"] + if root["right"] is None: + return root["left"] + temp = root["right"] + while temp["left"]: + temp = temp["left"] + root["name"] = temp["name"] + root["phone"] = temp["phone"] + root["right"] = bst_delete(root["right"], temp["name"]) + return root + + +def bst_list_all(root): + result = [] + + def walk(node): + if node is None: + return + walk(node["left"]) + result.append((node["name"], node["phone"])) + walk(node["right"]) + + walk(root) + return result + + +def make_records(n): + records = [] + for i in range(n): + records.append((f"User_{i:05d}", f"8-900-{i % 10000:04d}")) + return records + + +records_all = make_records(N) +records_shuffled = records_all[:] +random.shuffle(records_shuffled) +records_sorted = sorted(records_all) + +all_names = [name for name, phone in records_all] +find_existing = random.sample(all_names, 100) +find_missing = [f"None_{i}" for i in range(10)] +find_names = find_existing + find_missing +random.shuffle(find_names) +delete_names = random.sample(all_names, 50) + +all_results = [] +summary = [] + + +def build_structure(struct_type, records): + if struct_type == "LinkedList": + head = None + for name, phone in records: + head = ll_insert(head, name, phone) + return head + if struct_type == "HashTable": + buckets = [None] * BUCKET_SIZE + for name, phone in records: + ht_insert(buckets, name, phone) + return buckets + root = None + for name, phone in records: + root = bst_insert(root, name, phone) + return root + + +def do_find(struct_type, container, names): + for name in names: + if struct_type == "LinkedList": + ll_find(container, name) + elif struct_type == "HashTable": + ht_find(container, name) + else: + bst_find(container, name) + + +def do_delete(struct_type, container, names): + if struct_type == "LinkedList": + for name in names: + container = ll_delete(container, name) + return container + if struct_type == "HashTable": + for name in names: + ht_delete(container, name) + return container + for name in names: + container = bst_delete(container, name) + return container + + +def run_one_test(struct_type, mode_name, records): + ins_times = [] + find_times = [] + del_times = [] + + for run in range(REPEAT): + start = time.perf_counter() + container = build_structure(struct_type, records) + ins_times.append(time.perf_counter() - start) + + start = time.perf_counter() + do_find(struct_type, container, find_names) + find_times.append(time.perf_counter() - start) + + start = time.perf_counter() + do_delete(struct_type, container, delete_names) + del_times.append(time.perf_counter() - start) + + all_results.append([ + struct_type, mode_name, f"Run {run + 1}", + ins_times[-1], find_times[-1], del_times[-1], + ]) + + avg_ins = sum(ins_times) / REPEAT + avg_find = sum(find_times) / REPEAT + avg_del = sum(del_times) / REPEAT + + all_results.append([ + struct_type, mode_name, "AVERAGE", avg_ins, avg_find, avg_del, + ]) + summary.append({ + "name": struct_type, + "mode": mode_name, + "ins": avg_ins, + "find": avg_find, + "del": avg_del, + }) + + +print("Запуск экспериментов...") +for mode_name, data in [("случайный", records_shuffled), ("сортированный", records_sorted)]: + for struct_type in ["LinkedList", "HashTable", "BST"]: + print(f" {struct_type} ({mode_name})") + run_one_test(struct_type, mode_name, data) + +csv_path = os.path.join(DATA_PATH, "results.csv") +with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f, delimiter=";") + writer.writerow(["Структура", "Режим", "Итерация", "Вставка", "Поиск", "Удаление"]) + writer.writerows(all_results) + +print("CSV сохранён:", csv_path) + +try: + import matplotlib.pyplot as plt + + plt.rcParams["font.sans-serif"] = ["Segoe UI", "Arial", "Tahoma", "DejaVu Sans"] + plt.rcParams["axes.unicode_minus"] = False + + labels = ["insert", "find", "delete"] + structs = ["LinkedList", "HashTable", "BST"] + colors = ["#5dade2", "#e67e22", "#58d68d"] + + fig1, axs = plt.subplots(1, 3, figsize=(15, 5)) + fig1.suptitle("Влияние порядка данных") + + for i, s_name in enumerate(structs): + rand_d = next(r for r in summary if r["name"] == s_name and r["mode"] == "случайный") + sort_d = next(r for r in summary if r["name"] == s_name and r["mode"] == "сортированный") + x = [0, 1, 2] + w = 0.35 + axs[i].bar([p - w / 2 for p in x], [rand_d["ins"], rand_d["find"], rand_d["del"]], w, label="случайный") + axs[i].bar([p + w / 2 for p in x], [sort_d["ins"], sort_d["find"], sort_d["del"]], w, label="сортированный") + axs[i].set_title(s_name) + axs[i].set_xticks(x) + axs[i].set_xticklabels(labels) + axs[i].legend() + axs[i].grid(axis="y", alpha=0.3) + + plt.tight_layout() + plt.savefig(os.path.join(DATA_PATH, "order_impact.png")) + plt.close() + + fig2, axs2 = plt.subplots(1, 3, figsize=(15, 5)) + fig2.suptitle(f"Сравнение структур (N={N})") + + for i, key in enumerate(["ins", "find", "del"]): + vals = [] + names = [] + for r in summary: + names.append(f"{r['name']}\n({r['mode'][:4]})") + vals.append(r[key]) + axs2[i].bar(names, vals, color=colors * 2) + axs2[i].set_title(labels[i]) + axs2[i].tick_params(axis="x", rotation=20) + + plt.tight_layout() + plt.savefig(os.path.join(DATA_PATH, "struct_comparison.png")) + plt.close() + print("Графики сохранены") +except ImportError: + print("matplotlib не установлен") + +report_path = os.path.join(os.path.dirname(BASE), "report.md") +with open(report_path, "w", encoding="utf-8-sig") as f: + f.write("# Отчёт: сравнение структур данных\n\n") + f.write(f"N = {N}, повторов = {REPEAT}\n\n") + f.write("| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |\n") + f.write("| --- | --- | --- | --- | --- |\n") + for r in summary: + f.write( + f"| {r['name']} | {r['mode']} | {r['ins']:.6f} | {r['find']:.6f} | {r['del']:.6f} |\n" + ) + f.write("\n## Графики\n\n") + f.write("![Сравнение](data/struct_comparison.png)\n\n") + f.write("![Порядок данных](data/order_impact.png)\n\n") + f.write("## Выводы\n\n") + f.write("- BST на отсортированных данных сильно тормозит (вырождение дерева).\n") + f.write("- Хеш-таблица быстра на поиске и слабо зависит от порядка вставки.\n") + f.write("- Связный список медленный при поиске.\n") + f.write("- Для частого поиска предпочтительна хеш-таблица.\n") + +print("Отчёт:", report_path) +print("Готово.") diff --git a/rybakovaa/lab1/docs/data/order_impact.png b/rybakovaa/lab1/docs/data/order_impact.png new file mode 100644 index 0000000..b25a98a Binary files /dev/null and b/rybakovaa/lab1/docs/data/order_impact.png differ diff --git a/rybakovaa/lab1/docs/data/results.csv b/rybakovaa/lab1/docs/data/results.csv new file mode 100644 index 0000000..06e543e --- /dev/null +++ b/rybakovaa/lab1/docs/data/results.csv @@ -0,0 +1,37 @@ +Структура;Режим;Итерация;Вставка;Поиск;Удаление +LinkedList;случайный;Run 1;0.002766899997368455;0.027239699964411557;0.015202199923805892 +LinkedList;случайный;Run 2;0.0023452999303117394;0.02690729999449104;0.014689600095152855 +LinkedList;случайный;Run 3;0.0026440999936312437;0.028060800046660006;0.01486769993789494 +LinkedList;случайный;Run 4;0.002523000002838671;0.02711769996676594;0.014554499997757375 +LinkedList;случайный;Run 5;0.0022324000019580126;0.02935329999309033;0.015334900002926588 +LinkedList;случайный;AVERAGE;0.0025023399852216245;0.027735759993083774;0.01492977999150753 +HashTable;случайный;Run 1;0.0037400999572128057;7.149996235966682e-05;3.490003291517496e-05 +HashTable;случайный;Run 2;0.004325399990193546;9.180000051856041e-05;4.499999340623617e-05 +HashTable;случайный;Run 3;0.006647299975156784;9.760004468262196e-05;4.809990059584379e-05 +HashTable;случайный;Run 4;0.004817900015041232;8.430005982518196e-05;4.279997665435076e-05 +HashTable;случайный;Run 5;0.0045270000118762255;7.889990229159594e-05;3.660004585981369e-05 +HashTable;случайный;AVERAGE;0.004811539989896118;8.481999393552541e-05;4.147998988628388e-05 +BST;случайный;Run 1;0.020208499976433814;0.00017140002455562353;0.000107599887996912 +BST;случайный;Run 2;0.02269990008790046;0.00022380007430911064;0.0001463999506086111 +BST;случайный;Run 3;0.022515299962833524;0.00019350007642060518;0.00011879997327923775 +BST;случайный;Run 4;0.02134259999729693;0.00019699998665601015;0.0001330999657511711 +BST;случайный;Run 5;0.022310999920591712;0.00020180002320557833;0.00011969998013228178 +BST;случайный;AVERAGE;0.02181545998901129;0.00019750003702938556;0.00012511995155364274 +LinkedList;сортированный;Run 1;0.0014724000357091427;0.024460599990561604;0.016624199924990535 +LinkedList;сортированный;Run 2;0.0026603000005707145;0.02619360003154725;0.015555899939499795 +LinkedList;сортированный;Run 3;0.003988999989815056;0.026726300013251603;0.016439199913293123 +LinkedList;сортированный;Run 4;0.003310499945655465;0.024290600093081594;0.016939799999818206 +LinkedList;сортированный;Run 5;0.003344499971717596;0.02642290003132075;0.016576700028963387 +LinkedList;сортированный;AVERAGE;0.002955339988693595;0.02561880003195256;0.01642715996131301 +HashTable;сортированный;Run 1;0.00349499995354563;9.34000127017498e-05;5.8999983593821526e-05 +HashTable;сортированный;Run 2;0.004315900034271181;0.00011070002801716328;5.6999968364834785e-05 +HashTable;сортированный;Run 3;0.004093199968338013;8.140003774315119e-05;4.549999721348286e-05 +HashTable;сортированный;Run 4;0.004008699906989932;8.000002708286047e-05;4.539999645203352e-05 +HashTable;сортированный;Run 5;0.004412899957969785;7.609999738633633e-05;4.290009383112192e-05 +HashTable;сортированный;AVERAGE;0.004065139964222908;8.832002058625221e-05;4.996000789105892e-05 +BST;сортированный;Run 1;8.548112499993294;0.06775930000003427;0.03638990002218634 +BST;сортированный;Run 2;8.337813499965705;0.06507849995978177;0.03630929999053478 +BST;сортированный;Run 3;8.455186700099148;0.06767350004520267;0.036670299945399165 +BST;сортированный;Run 4;8.47301429999061;0.06812409998383373;0.037254099966958165 +BST;сортированный;Run 5;8.588394599966705;0.06450700003188103;0.03623760002665222 +BST;сортированный;AVERAGE;8.480504320003092;0.0666284800041467;0.03657223999034613 diff --git a/rybakovaa/lab1/docs/data/struct_comparison.png b/rybakovaa/lab1/docs/data/struct_comparison.png new file mode 100644 index 0000000..8164813 Binary files /dev/null and b/rybakovaa/lab1/docs/data/struct_comparison.png differ diff --git a/rybakovaa/lab1/docs/report.md b/rybakovaa/lab1/docs/report.md new file mode 100644 index 0000000..4b604e3 --- /dev/null +++ b/rybakovaa/lab1/docs/report.md @@ -0,0 +1,25 @@ +# Отчёт: сравнение структур данных + +N = 10000, повторов = 5 + +| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) | +| --- | --- | --- | --- | --- | +| LinkedList | случайный | 0.002502 | 0.027736 | 0.014930 | +| HashTable | случайный | 0.004812 | 0.000085 | 0.000041 | +| BST | случайный | 0.021815 | 0.000198 | 0.000125 | +| LinkedList | сортированный | 0.002955 | 0.025619 | 0.016427 | +| HashTable | сортированный | 0.004065 | 0.000088 | 0.000050 | +| BST | сортированный | 8.480504 | 0.066628 | 0.036572 | + +## Графики + +![Сравнение](data/struct_comparison.png) + +![Порядок данных](data/order_impact.png) + +## Выводы + +- BST на отсортированных данных сильно тормозит (вырождение дерева). +- Хеш-таблица быстра на поиске и слабо зависит от порядка вставки. +- Связный список медленный при поиске. +- Для частого поиска предпочтительна хеш-таблица. diff --git a/rybakovaa/lab2/docs/data/laba2.py b/rybakovaa/lab2/docs/data/laba2.py new file mode 100644 index 0000000..efcecf6 --- /dev/null +++ b/rybakovaa/lab2/docs/data/laba2.py @@ -0,0 +1,428 @@ +from abc import ABC, abstractmethod +from collections import deque +import heapq +import time +import csv +import random +import os + +BASE = os.path.dirname(os.path.abspath(__file__)) + + +class Cell: + def __init__(self, x, y): + self.x = x + self.y = y + self.isWall = False + self.isStart = False + self.isExit = False + + def isPassable(self): + return not self.isWall + + def __eq__(self, other): + if other is None: + return False + return self.x == other.x and self.y == other.y + + def __hash__(self): + 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, height): + self.width = width + self.height = height + self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)] + self.start = None + self.exit = None + + def getCell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.grid[x][y] + return None + + def getNeighbors(self, cell): + neighbors = [] + for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]: + n = self.getCell(cell.x + dx, cell.y + dy) + if n and n.isPassable(): + neighbors.append(n) + return neighbors + + +class MazeBuilder(ABC): + @abstractmethod + def buildFromFile(self, filename): + pass + + +class TextFileMazeBuilder(MazeBuilder): + def buildFromFile(self, filename): + with open(filename, "r", encoding="utf-8") as f: + lines = [line.rstrip("\n\r") for line in f.readlines()] + + height = len(lines) + width = len(lines[0]) if height > 0 else 0 + + for line in lines: + if len(line) != width: + raise ValueError("все строки должны быть одной длины") + + maze = Maze(width, height) + + for y in range(height): + for x in range(width): + ch = lines[y][x] + cell = maze.getCell(x, y) + if ch == "#": + cell.isWall = True + elif ch == " ": + cell.isWall = False + elif ch == "S": + cell.isWall = False + cell.isStart = True + maze.start = cell + elif ch == "E": + cell.isWall = False + cell.isExit = True + maze.exit = cell + else: + raise ValueError(f"неизвестный символ: {ch}") + + if maze.start is None: + raise ValueError("нет старта (S)") + if maze.exit is None: + raise ValueError("нет выхода (E)") + + return maze + + +class PathFindingStrategy(ABC): + @abstractmethod + def findPath(self, maze, start, exit_cell): + pass + + def _reconstruct(self, parent, exit_cell): + path = [] + curr = exit_cell + while curr is not None: + path.append(curr) + curr = parent.get(curr) + path.reverse() + return path + + +class BFSStrategy(PathFindingStrategy): + def findPath(self, maze, start, exit_cell): + if exit_cell is None: + return [] + queue = deque([start]) + visited = {start} + parent = {start: None} + + while queue: + curr = queue.popleft() + if curr == exit_cell: + return self._reconstruct(parent, exit_cell) + for n in maze.getNeighbors(curr): + if n not in visited: + visited.add(n) + parent[n] = curr + queue.append(n) + return [] + + +class DFSStrategy(PathFindingStrategy): + def findPath(self, maze, start, exit_cell): + if exit_cell is None: + return [] + stack = [start] + visited = {start} + parent = {start: None} + + while stack: + curr = stack.pop() + if curr == exit_cell: + return self._reconstruct(parent, exit_cell) + for n in maze.getNeighbors(curr): + if n not in visited: + visited.add(n) + parent[n] = curr + stack.append(n) + return [] + + +class AStarStrategy(PathFindingStrategy): + def _heuristic(self, cell, exit_cell): + return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y) + + def findPath(self, maze, start, exit_cell): + if exit_cell is None: + return [] + open_set = [] + heapq.heappush(open_set, (0, start)) + parent = {start: None} + g_score = {start: 0} + + while open_set: + curr = heapq.heappop(open_set)[1] + if curr == exit_cell: + return self._reconstruct(parent, exit_cell) + + for n in maze.getNeighbors(curr): + new_g = g_score[curr] + 1 + if n not in g_score or new_g < g_score[n]: + g_score[n] = new_g + parent[n] = curr + f = new_g + self._heuristic(n, exit_cell) + heapq.heappush(open_set, (f, n)) + return [] + + +class SearchStats: + def __init__(self, time_ms, visited, path_len): + self.time_ms = time_ms + self.visited_cells = visited + self.path_length = path_len + + +class MazeSolver: + def __init__(self, maze): + self.maze = maze + self.strategy = None + self.observers = [] + + def setStrategy(self, strategy): + self.strategy = strategy + + def attach(self, observer): + self.observers.append(observer) + + def notify(self, event): + for obs in self.observers: + obs.update(event) + + def solve(self): + if self.strategy is None: + raise ValueError("стратегия не выбрана") + + start_time = time.perf_counter() + path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit) + elapsed_ms = (time.perf_counter() - start_time) * 1000 + + stats = SearchStats(elapsed_ms, len(path), len(path)) + self.notify({"type": "path_found", "maze": self.maze, "path": path, "stats": stats}) + return path, stats + + +class Observer(ABC): + @abstractmethod + def update(self, event): + pass + + +class ConsoleView(Observer): + def update(self, event): + if event["type"] == "path_found": + stats = event["stats"] + print(f"длина пути {stats.path_length}, время {stats.time_ms:.2f} мс") + + +def save_maze(maze, filename): + path = os.path.join(BASE, filename) + with open(path, "w", encoding="utf-8") as f: + for y in range(maze.height): + line = "" + for x in range(maze.width): + cell = maze.getCell(x, y) + if cell == maze.start: + line += "S" + elif cell == maze.exit: + line += "E" + elif cell.isWall: + line += "#" + else: + line += " " + f.write(line + "\n") + + +def generate_with_walls(w, h, prob=0.3): + maze = Maze(w, h) + for x in range(w): + for y in range(h): + if random.random() < prob: + maze.getCell(x, y).isWall = True + maze.getCell(0, 0).isWall = False + maze.getCell(w - 1, h - 1).isWall = False + for x in range(w): + maze.getCell(x, 0).isWall = False + for y in range(h): + maze.getCell(w - 1, y).isWall = False + maze.getCell(0, 0).isStart = True + maze.start = maze.getCell(0, 0) + maze.getCell(w - 1, h - 1).isExit = True + maze.exit = maze.getCell(w - 1, h - 1) + return maze + + +def generate_empty(w, h): + maze = Maze(w, h) + for x in range(w): + for y in range(h): + maze.getCell(x, y).isWall = False + maze.getCell(0, 0).isStart = True + maze.start = maze.getCell(0, 0) + maze.getCell(w - 1, h - 1).isExit = True + maze.exit = maze.getCell(w - 1, h - 1) + return maze + + +def generate_no_exit(w, h): + maze = generate_with_walls(w, h, 0.3) + exit_cell = maze.getCell(w - 1, h - 1) + exit_cell.isWall = True + exit_cell.isExit = False + maze.exit = None + return maze + + +def run_experiment(maze, strategy_class, maze_name, repeats=5): + times = [] + path_lens = [] + + for _ in range(repeats): + solver = MazeSolver(maze) + solver.setStrategy(strategy_class()) + path, stats = solver.solve() + times.append(stats.time_ms) + path_lens.append(len(path)) + + raw = strategy_class.__name__ + strat_name = "A" if raw == "AStarStrategy" else raw.replace("Strategy", "") + return { + "лабиринт": maze_name, + "стратегия": strat_name, + "время_ср": sum(times) / repeats, + "длина_пути_ср": sum(path_lens) / repeats, + "путь_найден": any(l > 0 for l in path_lens), + } + + +def main(): + mazes = [] + + small = generate_with_walls(10, 10, 0.2) + save_maze(small, "maze_small.txt") + mazes.append(("маленький 10x10", small)) + + medium = generate_with_walls(50, 50, 0.3) + save_maze(medium, "maze_medium.txt") + mazes.append(("средний 50x50", medium)) + + large = generate_with_walls(100, 100, 0.3) + save_maze(large, "maze_large.txt") + mazes.append(("большой 100x100", large)) + + empty = generate_empty(50, 50) + save_maze(empty, "maze_empty.txt") + mazes.append(("пустой 50x50", empty)) + + no_exit = generate_no_exit(20, 20) + save_maze(no_exit, "maze_no_exit.txt") + mazes.append(("без выхода 20x20", no_exit)) + + strategies = [BFSStrategy, DFSStrategy, AStarStrategy] + results = [] + + for maze_name, maze in mazes: + print(maze_name) + for strat in strategies: + res = run_experiment(maze, strat, maze_name) + results.append(res) + print(f" {strat.__name__}: {res['время_ср']:.2f} мс") + + csv_path = os.path.join(BASE, "resultslab.csv") + with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.DictWriter( + f, + fieldnames=["лабиринт", "стратегия", "время_ср", "длина_пути_ср", "путь_найден"], + delimiter=";", + ) + writer.writeheader() + for row in results: + row_ru = row.copy() + row_ru["путь_найден"] = "да" if row["путь_найден"] else "нет" + writer.writerow(row_ru) + + try: + import matplotlib.pyplot as plt + + plt.rcParams["font.sans-serif"] = ["Segoe UI", "Arial", "Tahoma", "DejaVu Sans"] + plt.rcParams["axes.unicode_minus"] = False + + labyrinths = [] + for r in results: + if r["лабиринт"] not in labyrinths: + labyrinths.append(r["лабиринт"]) + + fig, axes = plt.subplots(1, len(labyrinths), figsize=(4 * len(labyrinths), 4)) + if len(labyrinths) == 1: + axes = [axes] + + for idx, lab in enumerate(labyrinths): + times = [] + for s in ["BFS", "DFS", "A"]: + for r in results: + if r["лабиринт"] == lab and r["стратегия"] == s: + times.append(r["время_ср"]) + break + axes[idx].bar(["BFS", "DFS", "A"], times, color=["#1a5632", "#0e5fb4", "#e67e22"]) + axes[idx].set_title(lab) + axes[idx].set_ylabel("мс") + + plt.tight_layout() + plt.savefig(os.path.join(BASE, "maze_time_comparison.png")) + plt.close() + except ImportError: + pass + + report_path = os.path.join(os.path.dirname(BASE), "report.md") + with open(report_path, "w", encoding="utf-8-sig") as f: + f.write("# Отчёт: поиск пути в лабиринте\n\n") + f.write("Паттерны: Builder, Strategy, Observer\n\n") + f.write("```mermaid\nclassDiagram\n") + f.write("class MazeBuilder\nclass TextFileMazeBuilder\n") + f.write("class PathFindingStrategy\nclass BFSStrategy\n") + f.write("class DFSStrategy\nclass AStarStrategy\n") + f.write("class MazeSolver\nclass Observer\nclass ConsoleView\n") + f.write("MazeBuilder <|-- TextFileMazeBuilder\n") + f.write("PathFindingStrategy <|-- BFSStrategy\n") + f.write("PathFindingStrategy <|-- DFSStrategy\n") + f.write("PathFindingStrategy <|-- AStarStrategy\n") + f.write("Observer <|-- ConsoleView\n") + f.write("MazeSolver --> PathFindingStrategy\n") + f.write("```\n\n") + f.write("| Лабиринт | Стратегия | Время (мс) | Длина пути | Найден |\n") + f.write("| --- | --- | --- | --- | --- |\n") + for r in results: + found = "да" if r["путь_найден"] else "нет" + f.write( + f"| {r['лабиринт']} | {r['стратегия']} | {r['время_ср']:.2f} | " + f"{r['длина_пути_ср']:.0f} | {found} |\n" + ) + f.write("\n![График](data/maze_time_comparison.png)\n\n") + f.write("## Выводы\n\n") + f.write("- BFS и A* находят кратчайший путь.\n") + f.write("- DFS путь может быть длиннее.\n") + f.write("- На пустом лабиринте алгоритмы работают быстрее всего.\n") + f.write("- Без выхода все стратегии возвращают пустой путь.\n") + + print("Готово:", report_path) + + +if __name__ == "__main__": + main() diff --git a/rybakovaa/lab2/docs/data/maze_empty.txt b/rybakovaa/lab2/docs/data/maze_empty.txt new file mode 100644 index 0000000..a92cf1d --- /dev/null +++ b/rybakovaa/lab2/docs/data/maze_empty.txt @@ -0,0 +1,50 @@ +S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E diff --git a/rybakovaa/lab2/docs/data/maze_large.txt b/rybakovaa/lab2/docs/data/maze_large.txt new file mode 100644 index 0000000..5742515 --- /dev/null +++ b/rybakovaa/lab2/docs/data/maze_large.txt @@ -0,0 +1,100 @@ +S + # # # ## # # # # # # # # # ## # # # # ### # ## #### + ## # # # # # # # # ## # # # ### ## ## # ## # # #### +# # # # # # # # # # # # # # # # # # ## # ## # # + # # ## # # ## # # # ## # ## # ## # ## ## # + ### ## # ### ### # # # ## # # # ## ## # ### ## # # + ### # # # #### ## # # # # # ## # # # # # # +### # ## # # ## # # # ## # ## # # #### # # # ### # # # ## # + # # ## # # ## # # # # # ##### #### # #### # ## # # + ## # # # # # # ## # ## # ## # # # ## # # # # + # # ## # # ## # # ### # # # # # # # ### # # ## +# # # # # ## # # # ## # ## # # ### # # # # # # # ## # + # # # ### ## ## ## # # # # # # # # # # # # # ## # ## + # ## ## # # # # # # ## ## # ## # # ## # # # ### + # ## # # # # ## # # # # # # # # # # # ### # ## +# # # # # # # # #### ### # # ## # ## ## # # + # ### # # # # # ## # # #### # # # # ### # # # # # # ## +# # # # ## # # # # # ## # # # # ## ## ### # + # # ### # ## ## # # # # # # # # # # # # # + ## ## # # # # # ## # ## # # ## ## # ## # # # # # + #### ## # ### # # # # ## # # ## # # ## # # # # # # # # ## +## # ### # # # ## # # # ## # # # # # # ### ## ## # ### + ### #### # # # # # ## ## ### ### ## ## # ## # # ### ## # # # +# #### ## # # # ### # # # # # # # ## # # ##### # ## # ## + ## ### # ## ## # # ## # # # ## # # ## # # #### ### ## ## # + ## ## # # # # # # # # # ## ###### # ## ## ### #### # + # # ## # # # ## # # # # ### # ### # # ## # # # # ## # + # ## ## # # # # # ## # # ## # # ## # # # #### # # # # + # #### # ### # # # # #### # ## # # ## ### # # # # # # #### + ## # # # # # # # ### # ## # ## # # ## # # # ## ## # # +## # # # # # ## # # # # # # # # ## # # # # # # ### +# ## # # # # #### # # ## ## ## # ## # # ### # # ## # # # # + # # # # # # ## # # ## ### ## # # # ## ## # ## # # ## # ## # ## +# # ## ## # ## # ## # ### # ### ## ## ## ## # ## # # + # # # # # # ## # ###### # ## # # ## # # ## ## + ## ## # # # # # ## ## # # # #### ## ## # # # ## ## + ### # # ## # # # # ## # # # # # # ## # ## ## ## + ## # # # # # ## # # # # # ### # ## ### ## # ## # # # # # # ## +# ### # # # # ### ## # # # ## ## ### # ## # # # # + ##### ### # # # ###### # # ## # # # # ## # # # # ##### # + # # # # # # ## # #### # ### # ## #### # # # # # +# ### # # # # # # # # # #### # # ## # # # # # # # # # +## # ### # # # # # ## # # # # ### # # # # # # # # ## # # # # # + # # # # # # #### # # # ## ### # # # ## # ### # # ### +# # ## # ## # # # # # # ## # # # ## # # # # # ## ## + # # # # # # # # ## # # # ### # # ###### # # # # ## # + # # # # # ## # #### # # ## # ## ## # ## # # ## # + ### ## # # # ### # # # ## # # ### ### # # # # # ## + ## # # ### # # # ## # ## # # ####### # # #### # # # # # ## + # ## # # # ### # ## # # ## # # # # # ## ## ### # + ## ### # # ## # # # ## # ## # ## # ## # ## # ### + # # # # ## # # # # # # # # ## ##### # ## ### # ### # ### # # # # +## # # # # # # ##### ## # ### # ## ### # # # ## +## ## ## # # # # # # # # ## # ### ## # # # # # + ## # # # # # # # ## # #### # # # # # # # # # # # # ### + # # # # # # # ## ### # # ##### # # # # # # ## ## # ## # # # + # # # # # # # # # # # # ## # # # # + # # ## # ## # ## # # ## ##### # # # # # # ## # # # + # ### # ## # ### ## # # # # # ## # # ### ## # # # # + ### # # # ## ## # # # # # #### # ## # # # # ### ## + # # ## ## # ## # ## ### # # # # # # # # # ## # # # # + ## # # ## # # # ## ### # # # # ### # # # # # # # # # +# # ## ### ### # ## # # # # # # # # # # # # # # # # ### # + # # # ## # # ## # # # # # # # ## # + # # # ## ## # # # # # ## ## # # # ## ### # # # # # ### +## # ### # # ## # ## # # # # # # # # # # # # +# # ## # # # # # # # # # # # # # ## # # # # + ## # # # # ## # # # ### # # ## # # ## # ### # + # ## # # # ## # # ## ## # # # # ## # ## # # # # + # # # # # # ## # ## # ### ## # ## # # ## # # # # # +### # # # # # # # # ##### # ## # # # # # # # ## # ### # # # # +# # ## # # # ## # ## # # ### # # ## # # # # # # # # # + # # # ## # ## # # # # # ### ## # # # # # ## # + # ### # ## # # ## # # # # ## # ## ## # # # +# # # # # # # ### # # # ## # # ### # # #### # # +## ### # # # ## # # # # ## # # # # # # # ### # # # # ## # # # +# ## # # # # # # # # # # # # # # # # ### + # # # # ### # # # # # # # # # # ### # ## # ### # # # + # # ## # # # # # # # # ## # ## # # ### # # # # # + # # ## ## # # # # # # # # ## # ## # ## # ## ## # # # # # + # ## # # ## # ## # # # ### # # ## #### ### # # # +## # # # # ### # # ## # # ## # # # ## # # # # # ## # # + ## # ## # ### # ### # # # # # ## ## # # # # # ### # # # +## ### # # # # ### # # # # # # ## # # # ## ## # # ## # # ### ### + # ## # # # # ## # # # # # ### # # # # # # # # ### + ## ### # ## # # ## # # ## # ## # # # ### # # # # ## + # # # ## ##### # ## # ## # # # ### # ## # ## # ## ## # # ### + # # ## # ### # # # ## ## # # # # # # # # # # +# # # ### # # # # # ## # ## # #### ### # # # # # ### # ## # # + # # # # # # # # # # ## ## # # ## # # # # ## ## ## # # +# # # # # # ## # # # ##### # # ## ## # # ## # ## # # + # ### ## ## # #### ### # # ### # ### # # # ## ### #### ## ## ## ### + # # # # # # # ## # # # # # # # # # ## +# # # # ## # ## # # # # # # # # ## ## # # # +# # ## # # # # # ## # # # # ## ## # # # # ## + ## # # # # # # ## # # ### ## ### # ## # + # # # # # # # # # # # # # # ## ## # # # # + ## # # # ### # ### # # # # # ## ## # # # # # ## # # # # + # # ## # # ## # ### # # # # ### # # ## ### + # # # # # # # # # ## # # # # # # # ## # # # ## ## E diff --git a/rybakovaa/lab2/docs/data/maze_medium.txt b/rybakovaa/lab2/docs/data/maze_medium.txt new file mode 100644 index 0000000..6c46068 --- /dev/null +++ b/rybakovaa/lab2/docs/data/maze_medium.txt @@ -0,0 +1,50 @@ +S + # # # #### # # # # + ### # # # # # # ## ## ### # + # ## # # # # ## # #### # + ## ## # # # # ## # +# ## # # ### ## ## ### ## ## #### # + ##### # # # # # ## ## # # # ### + ### ## ## ## # # ## ### # + ## # ### # ### # ## # # # # ### ### + ### # # # # ## # ### # # + # ## # # # # # # # # + # # ### # ## # ## # # +# # # ## # # # # # # ### +## ### ### ## ## ## # # # + # # ## ## # # ## # ## ## ## # + # # # # ## # # # ## # # ## + # ## # # # ## # # +## # # # ## # # # #### # ##### # ## # +# # ## # # # # ##### ## # # + # # ## ## # # ## # # + # # ## ### # # # # # ### # + ## # # # ### # # ## # ## +# # # # ## # # # ## # # ## # ## + # # ## ## # #### # +# # # ## ### # ## # # # +# # ### # # ## # # ### # # ## # +# # # ## # ## # # # # # # # + # ## # # # # # # # # +# ## # #### # # # # ## # + # # ## # ### # # # ### ## ## # +# # # ### ## # # # # ## # +## # # ### ## # # # +### # # ## ## # # # # + # # # # # # # # # ## # # + # # ## ## # # ## # #### # # + # # # # ## # # # ## ## ## +# # # ## # ### # ### + # # ## ## # # # # # ## + # # # # # # # # ## # # # + ### ##### ## ## # # ### # # ## # # + ## # ### ## # # # #### # # + # # # # # # # ## ## ## + # # ## # # # ## ### ## ## # + ## # # ### # # ## ### +## # # # # # # # # # ## # # #### ## + # # # # # ## # ## ## # +## # # # # # # ## # # # ## # # +# ## # ### # # ## # # ### ## +## # # # ## # # # # # + # # # ### # ## # # ## E diff --git a/rybakovaa/lab2/docs/data/maze_no_exit.txt b/rybakovaa/lab2/docs/data/maze_no_exit.txt new file mode 100644 index 0000000..5d1c300 --- /dev/null +++ b/rybakovaa/lab2/docs/data/maze_no_exit.txt @@ -0,0 +1,20 @@ +S +# # # # ### + # # + # # # ## # + ## # ## # + # # ## + # ## + # ## # # + # # + ## # # + ### # # + # # # # ### + # ## +# # ## ## + # # #### + # # # # # + # # # + # ## ## + ## ## # # # +# # # ## diff --git a/rybakovaa/lab2/docs/data/maze_small.txt b/rybakovaa/lab2/docs/data/maze_small.txt new file mode 100644 index 0000000..dc8788d --- /dev/null +++ b/rybakovaa/lab2/docs/data/maze_small.txt @@ -0,0 +1,10 @@ +S + + # # +## # # +## # + # +# ## + +# # # + ### E diff --git a/rybakovaa/lab2/docs/data/maze_time_comparison.png b/rybakovaa/lab2/docs/data/maze_time_comparison.png new file mode 100644 index 0000000..d962167 Binary files /dev/null and b/rybakovaa/lab2/docs/data/maze_time_comparison.png differ diff --git a/rybakovaa/lab2/docs/data/resultslab.csv b/rybakovaa/lab2/docs/data/resultslab.csv new file mode 100644 index 0000000..763fd2f --- /dev/null +++ b/rybakovaa/lab2/docs/data/resultslab.csv @@ -0,0 +1,16 @@ +лабиринт;стратегия;время_ср;длина_пути_ср;путь_найден +маленький 10x10;BFS;0.14045997522771358;19.0;да +маленький 10x10;DFS;0.08256000000983477;37.0;да +маленький 10x10;A;0.2506999997422099;19.0;да +средний 50x50;BFS;2.8775800252333283;99.0;да +средний 50x50;DFS;1.9064400112256408;283.0;да +средний 50x50;A;2.429639990441501;99.0;да +большой 100x100;BFS;12.2316999360919;199.0;да +большой 100x100;DFS;8.781959977932274;1643.0;да +большой 100x100;A;8.597399992868304;199.0;да +пустой 50x50;BFS;4.875819990411401;99.0;да +пустой 50x50;DFS;3.1325000105425715;1275.0;да +пустой 50x50;A;11.547920037992299;99.0;да +без выхода 20x20;BFS;0.0002400018274784088;0.0;нет +без выхода 20x20;DFS;0.0002400018274784088;0.0;нет +без выхода 20x20;A;0.0001600012183189392;0.0;нет diff --git a/rybakovaa/lab2/docs/report.md b/rybakovaa/lab2/docs/report.md new file mode 100644 index 0000000..93f0071 --- /dev/null +++ b/rybakovaa/lab2/docs/report.md @@ -0,0 +1,49 @@ +# Отчёт: поиск пути в лабиринте + +Паттерны: Builder, Strategy, Observer + +```mermaid +classDiagram +class MazeBuilder +class TextFileMazeBuilder +class PathFindingStrategy +class BFSStrategy +class DFSStrategy +class AStarStrategy +class MazeSolver +class Observer +class ConsoleView +MazeBuilder <|-- TextFileMazeBuilder +PathFindingStrategy <|-- BFSStrategy +PathFindingStrategy <|-- DFSStrategy +PathFindingStrategy <|-- AStarStrategy +Observer <|-- ConsoleView +MazeSolver --> PathFindingStrategy +``` + +| Лабиринт | Стратегия | Время (мс) | Длина пути | Найден | +| --- | --- | --- | --- | --- | +| маленький 10x10 | BFS | 0.14 | 19 | да | +| маленький 10x10 | DFS | 0.08 | 37 | да | +| маленький 10x10 | A | 0.25 | 19 | да | +| средний 50x50 | BFS | 2.88 | 99 | да | +| средний 50x50 | DFS | 1.91 | 283 | да | +| средний 50x50 | A | 2.43 | 99 | да | +| большой 100x100 | BFS | 12.23 | 199 | да | +| большой 100x100 | DFS | 8.78 | 1643 | да | +| большой 100x100 | A | 8.60 | 199 | да | +| пустой 50x50 | BFS | 4.88 | 99 | да | +| пустой 50x50 | DFS | 3.13 | 1275 | да | +| пустой 50x50 | A | 11.55 | 99 | да | +| без выхода 20x20 | BFS | 0.00 | 0 | нет | +| без выхода 20x20 | DFS | 0.00 | 0 | нет | +| без выхода 20x20 | A | 0.00 | 0 | нет | + +![График](data/maze_time_comparison.png) + +## Выводы + +- BFS и A* находят кратчайший путь. +- DFS путь может быть длиннее. +- На пустом лабиринте алгоритмы работают быстрее всего. +- Без выхода все стратегии возвращают пустой путь.