Merge pull request '[2] final' (#382) from Smirnovvs/2026-rff_mp:4 into develop

Reviewed-on: UNN/2026-rff_mp#382
This commit is contained in:
git_admin 2026-09-05 06:37:33 +00:00
commit 70dd7d781a
18 changed files with 1005 additions and 0 deletions

1
SmirnovVS/428.md Normal file
View File

@ -0,0 +1 @@
428

View File

@ -0,0 +1,132 @@
"""Экспериментальное сравнение структур данных."""
import argparse
import csv
import random
from pathlib import Path
from statistics import mean
from time import perf_counter
from phonebook import (
bst_delete, bst_find, bst_insert,
ht_delete, ht_find, ht_insert,
ll_delete, ll_find, ll_insert,
)
def generate_records(size):
return [(f"User_{index:05d}", f"+7{index:010d}") for index in range(size)]
def measure_once(structure, records, existing_names, missing_names, deleted_names, bucket_count):
started = perf_counter()
if structure == "LinkedList":
data = None
for name, phone in records:
data = ll_insert(data, name, phone)
insert_time = perf_counter() - started
started = perf_counter()
for name in existing_names + missing_names:
ll_find(data, name)
find_time = perf_counter() - started
started = perf_counter()
for name in deleted_names:
data = ll_delete(data, name)
delete_time = perf_counter() - started
elif structure == "HashTable":
data = [None] * bucket_count
for name, phone in records:
ht_insert(data, name, phone)
insert_time = perf_counter() - started
started = perf_counter()
for name in existing_names + missing_names:
ht_find(data, name)
find_time = perf_counter() - started
started = perf_counter()
for name in deleted_names:
ht_delete(data, name)
delete_time = perf_counter() - started
else:
data = None
for name, phone in records:
data = bst_insert(data, name, phone)
insert_time = perf_counter() - started
started = perf_counter()
for name in existing_names + missing_names:
bst_find(data, name)
find_time = perf_counter() - started
started = perf_counter()
for name in deleted_names:
data = bst_delete(data, name)
delete_time = perf_counter() - started
return {"insert": insert_time, "find_110": find_time, "delete_50": delete_time}
def run_experiment(size=3000, repeats=5, seed=2026, output_dir="docs/data"):
rng = random.Random(seed)
sorted_records = generate_records(size)
shuffled_records = sorted_records.copy()
rng.shuffle(shuffled_records)
modes = {"shuffled": shuffled_records, "sorted": sorted_records}
rows = []
for mode, records in modes.items():
names = [record[0] for record in records]
test_cases = []
for run in range(1, repeats + 1):
test_cases.append((
run,
rng.sample(names, min(100, size)),
[f"None_{index}" for index in range(10)],
rng.sample(names, min(50, size)),
))
for structure in ("LinkedList", "HashTable", "BST"):
for run, existing, missing, deleted in test_cases:
timings = measure_once(structure, records, existing, missing, deleted, max(17, size * 2 + 1))
for operation, elapsed in timings.items():
rows.append({
"structure": structure,
"mode": mode,
"operation": operation,
"run": run,
"time_seconds": elapsed,
})
output = Path(output_dir)
output.mkdir(parents=True, exist_ok=True)
raw_path = output / "results_raw.csv"
with raw_path.open("w", newline="", encoding="utf-8-sig") as file:
writer = csv.DictWriter(file, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
groups = {}
for row in rows:
key = (row["structure"], row["mode"], row["operation"])
groups.setdefault(key, []).append(row["time_seconds"])
summary = [
{"structure": key[0], "mode": key[1], "operation": key[2], "mean_seconds": mean(values)}
for key, values in groups.items()
]
summary_path = output / "results_summary.csv"
with summary_path.open("w", newline="", encoding="utf-8-sig") as file:
writer = csv.DictWriter(file, fieldnames=summary[0].keys())
writer.writeheader()
writer.writerows(summary)
return raw_path, summary_path
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Сравнение структур телефонного справочника")
parser.add_argument("--size", type=int, default=3000)
parser.add_argument("--repeats", type=int, default=5)
args = parser.parse_args()
raw, summary = run_experiment(args.size, args.repeats)
print(f"Полные замеры: {raw}\nСредние значения: {summary}")

View File

@ -0,0 +1,25 @@
from phonebook import ht_delete, ht_find, ht_insert, ht_list_all
def main():
phonebook = [None] * 101
print("Телефонный справочник. Команды: add, find, delete, list, exit")
while True:
command = input("> ").strip().lower()
if command == "add":
ht_insert(phonebook, input("Имя: ").strip(), input("Телефон: ").strip())
elif command == "find":
print(ht_find(phonebook, input("Имя: ").strip()) or "Запись не найдена")
elif command == "delete":
ht_delete(phonebook, input("Имя: ").strip())
elif command == "list":
for name, phone in ht_list_all(phonebook):
print(f"{name}: {phone}")
elif command == "exit":
break
else:
print("Неизвестная команда")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,162 @@
"""Телефонный справочник на трёх структурах данных."""
def _node(name, phone, next_node=None):
return {"name": name, "phone": phone, "next": next_node}
def ll_insert(head, name, phone):
"""Добавить запись в конец списка или обновить существующую."""
if head is None:
return _node(name, phone)
current = head
while True:
if current["name"] == name:
current["phone"] = phone
return head
if current["next"] is None:
current["next"] = _node(name, phone)
return head
current = current["next"]
def ll_find(head, name):
current = head
while current is not None:
if current["name"] == name:
return current["phone"]
current = current["next"]
return None
def ll_delete(head, name):
if head is None:
return None
if head["name"] == name:
return head["next"]
previous, current = head, head["next"]
while current is not None:
if current["name"] == name:
previous["next"] = current["next"]
break
previous, current = current, current["next"]
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current["name"], current["phone"]))
current = current["next"]
return sorted(records, key=lambda record: record[0])
def _require_buckets(buckets):
if not buckets:
raise ValueError("Хеш-таблица должна содержать хотя бы один бакет")
def _hash_index(name, bucket_count):
"""Хеш-функция."""
value = 0
for character in name:
value = (value * 31 + ord(character)) % bucket_count
return value
def ht_insert(buckets, name, phone):
_require_buckets(buckets)
index = _hash_index(name, len(buckets))
buckets[index] = ll_insert(buckets[index], name, phone)
def ht_find(buckets, name):
_require_buckets(buckets)
return ll_find(buckets[_hash_index(name, len(buckets))], name)
def ht_delete(buckets, name):
_require_buckets(buckets)
index = _hash_index(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
def ht_list_all(buckets):
_require_buckets(buckets)
records = []
for head in buckets:
records.extend(ll_list_all(head))
return sorted(records, key=lambda record: record[0])
def _bst_node(name, phone):
return {"name": name, "phone": phone, "left": None, "right": None}
def bst_insert(root, name, phone):
if root is None:
return _bst_node(name, phone)
current = root
while True:
if name == current["name"]:
current["phone"] = phone
return root
side = "left" if name < current["name"] else "right"
if current[side] is None:
current[side] = _bst_node(name, phone)
return root
current = current[side]
def bst_find(root, name):
current = root
while current is not None:
if name == current["name"]:
return current["phone"]
current = current["left"] if name < current["name"] else current["right"]
return None
def bst_delete(root, name):
parent = None
current = root
while current is not None and current["name"] != name:
parent = current
current = current["left"] if name < current["name"] else current["right"]
if current is None:
return root
if current["left"] is not None and current["right"] is not None:
successor_parent = current
successor = current["right"]
while successor["left"] is not None:
successor_parent = successor
successor = successor["left"]
current["name"], current["phone"] = successor["name"], successor["phone"]
parent, current = successor_parent, successor
child = current["left"] if current["left"] is not None else current["right"]
if parent is None:
return child
if parent["left"] is current:
parent["left"] = child
else:
parent["right"] = child
return root
def bst_list_all(root):
records, stack = [], []
current = root
while stack or current is not None:
while current is not None:
stack.append(current)
current = current["left"]
current = stack.pop()
records.append((current["name"], current["phone"]))
current = current["right"]
return records

View File

@ -0,0 +1,32 @@
import csv
from pathlib import Path
import matplotlib.pyplot as plt
def create_plot(csv_path="docs/data/results_summary.csv", output_path="docs/data/performance.png"):
with Path(csv_path).open(encoding="utf-8-sig") as file:
rows = list(csv.DictReader(file))
operations = ["insert", "find_110", "delete_50"]
titles = ["Вставка всех записей", "Поиск 110 имён", "Удаление 50 записей"]
labels = [(structure, mode) for mode in ("shuffled", "sorted") for structure in ("LinkedList", "HashTable", "BST")]
figure, axes = plt.subplots(1, 3, figsize=(15, 5))
for axis, operation, title in zip(axes, operations, titles):
values = []
for structure, mode in labels:
row = next(item for item in rows if item["structure"] == structure and item["mode"] == mode and item["operation"] == operation)
values.append(float(row["mean_seconds"]) * 1000)
axis.bar(range(len(labels)), values, color=["#4472C4", "#70AD47", "#ED7D31"] * 2)
axis.set_title(title)
axis.set_ylabel("Время, мс")
axis.set_xticks(range(len(labels)), [f"{s}\n{m}" for s, m in labels], rotation=35, ha="right")
axis.grid(axis="y", alpha=0.25)
figure.suptitle("Производительность структур данных (средние значения)")
figure.tight_layout()
figure.savefig(output_path, dpi=180)
plt.close(figure)
return Path(output_path)
if __name__ == "__main__":
print(f"График сохранён: {create_plot()}")

View File

@ -0,0 +1,91 @@
structure,mode,operation,run,time_seconds
LinkedList,shuffled,insert,1,0.1947654000005059
LinkedList,shuffled,find_110,1,0.0063459000002694665
LinkedList,shuffled,delete_50,1,0.002933899999334244
LinkedList,shuffled,insert,2,0.19509340000058728
LinkedList,shuffled,find_110,2,0.006126699999185803
LinkedList,shuffled,delete_50,2,0.002876999999898544
LinkedList,shuffled,insert,3,0.19163060000028054
LinkedList,shuffled,find_110,3,0.00638989999970363
LinkedList,shuffled,delete_50,3,0.0027322000005369773
LinkedList,shuffled,insert,4,0.19537710000076913
LinkedList,shuffled,find_110,4,0.00553089999993972
LinkedList,shuffled,delete_50,4,0.00274009999975533
LinkedList,shuffled,insert,5,0.19868119999955525
LinkedList,shuffled,find_110,5,0.006262500000048021
LinkedList,shuffled,delete_50,5,0.0034435999996276223
HashTable,shuffled,insert,1,0.0045235999996293685
HashTable,shuffled,find_110,1,0.00012590000005729962
HashTable,shuffled,delete_50,1,6.089999988034833e-05
HashTable,shuffled,insert,2,0.004393600000184961
HashTable,shuffled,find_110,2,0.00011859999995067483
HashTable,shuffled,delete_50,2,5.79999996261904e-05
HashTable,shuffled,insert,3,0.0038748000006307848
HashTable,shuffled,find_110,3,0.00011610000001383014
HashTable,shuffled,delete_50,3,5.719999990105862e-05
HashTable,shuffled,insert,4,0.003696299999319308
HashTable,shuffled,find_110,4,0.00011779999931604834
HashTable,shuffled,delete_50,4,5.729999975301325e-05
HashTable,shuffled,insert,5,0.0037232000004223664
HashTable,shuffled,find_110,5,0.0001156999996965169
HashTable,shuffled,delete_50,5,5.5900000006658956e-05
BST,shuffled,insert,1,0.004337399999712943
BST,shuffled,find_110,1,0.0001140000003942987
BST,shuffled,delete_50,1,7.019999975454994e-05
BST,shuffled,insert,2,0.004390000000057626
BST,shuffled,find_110,2,0.00011150000045745401
BST,shuffled,delete_50,2,6.52999997328152e-05
BST,shuffled,insert,3,0.004179900000053749
BST,shuffled,find_110,3,0.00010879999990720535
BST,shuffled,delete_50,3,6.070000017643906e-05
BST,shuffled,insert,4,0.004183999999440857
BST,shuffled,find_110,4,0.00010620000011840602
BST,shuffled,delete_50,4,9.539999973640079e-05
BST,shuffled,insert,5,0.004391600000417384
BST,shuffled,find_110,5,0.00010419999944133451
BST,shuffled,delete_50,5,6.630000007135095e-05
LinkedList,sorted,insert,1,0.19158669999978883
LinkedList,sorted,find_110,1,0.006112799999755225
LinkedList,sorted,delete_50,1,0.005895900000723486
LinkedList,sorted,insert,2,0.19256610000047658
LinkedList,sorted,find_110,2,0.006044900000233611
LinkedList,sorted,delete_50,2,0.002797499999360298
LinkedList,sorted,insert,3,0.19292270000005374
LinkedList,sorted,find_110,3,0.006467600000178209
LinkedList,sorted,delete_50,3,0.0028355000004012254
LinkedList,sorted,insert,4,0.19164809999983845
LinkedList,sorted,find_110,4,0.005796100000225124
LinkedList,sorted,delete_50,4,0.0030335999999806518
LinkedList,sorted,insert,5,0.1920518999995693
LinkedList,sorted,find_110,5,0.006033799999386247
LinkedList,sorted,delete_50,5,0.0028001999999105465
HashTable,sorted,insert,1,0.0036538000003929483
HashTable,sorted,find_110,1,0.0001173999999082298
HashTable,sorted,delete_50,1,5.810000038763974e-05
HashTable,sorted,insert,2,0.003586599999835016
HashTable,sorted,find_110,2,0.00011629999971773941
HashTable,sorted,delete_50,2,5.540000074688578e-05
HashTable,sorted,insert,3,0.00354940000033821
HashTable,sorted,find_110,3,0.00011549999999260763
HashTable,sorted,delete_50,3,5.520000013348181e-05
HashTable,sorted,insert,4,0.0036109000002397806
HashTable,sorted,find_110,4,0.00011329999961162684
HashTable,sorted,delete_50,4,5.6000000768108293e-05
HashTable,sorted,insert,5,0.003564399999959278
HashTable,sorted,find_110,5,0.00011339999946358148
HashTable,sorted,delete_50,5,5.480000072566327e-05
BST,sorted,insert,1,0.3111501000003045
BST,sorted,find_110,1,0.007159199999478005
BST,sorted,delete_50,1,0.004548900000372669
BST,sorted,insert,2,0.30066009999973176
BST,sorted,find_110,2,0.008023899999898276
BST,sorted,delete_50,2,0.004239900000357011
BST,sorted,insert,3,0.3020464000001084
BST,sorted,find_110,3,0.00876090000019758
BST,sorted,delete_50,3,0.004445499999746971
BST,sorted,insert,4,0.30183889999989333
BST,sorted,find_110,4,0.007328300000153831
BST,sorted,delete_50,4,0.005208799999309122
BST,sorted,insert,5,0.3034312999998292
BST,sorted,find_110,5,0.008107399999971676
BST,sorted,delete_50,5,0.004352900000412774
1 structure mode operation run time_seconds
2 LinkedList shuffled insert 1 0.1947654000005059
3 LinkedList shuffled find_110 1 0.0063459000002694665
4 LinkedList shuffled delete_50 1 0.002933899999334244
5 LinkedList shuffled insert 2 0.19509340000058728
6 LinkedList shuffled find_110 2 0.006126699999185803
7 LinkedList shuffled delete_50 2 0.002876999999898544
8 LinkedList shuffled insert 3 0.19163060000028054
9 LinkedList shuffled find_110 3 0.00638989999970363
10 LinkedList shuffled delete_50 3 0.0027322000005369773
11 LinkedList shuffled insert 4 0.19537710000076913
12 LinkedList shuffled find_110 4 0.00553089999993972
13 LinkedList shuffled delete_50 4 0.00274009999975533
14 LinkedList shuffled insert 5 0.19868119999955525
15 LinkedList shuffled find_110 5 0.006262500000048021
16 LinkedList shuffled delete_50 5 0.0034435999996276223
17 HashTable shuffled insert 1 0.0045235999996293685
18 HashTable shuffled find_110 1 0.00012590000005729962
19 HashTable shuffled delete_50 1 6.089999988034833e-05
20 HashTable shuffled insert 2 0.004393600000184961
21 HashTable shuffled find_110 2 0.00011859999995067483
22 HashTable shuffled delete_50 2 5.79999996261904e-05
23 HashTable shuffled insert 3 0.0038748000006307848
24 HashTable shuffled find_110 3 0.00011610000001383014
25 HashTable shuffled delete_50 3 5.719999990105862e-05
26 HashTable shuffled insert 4 0.003696299999319308
27 HashTable shuffled find_110 4 0.00011779999931604834
28 HashTable shuffled delete_50 4 5.729999975301325e-05
29 HashTable shuffled insert 5 0.0037232000004223664
30 HashTable shuffled find_110 5 0.0001156999996965169
31 HashTable shuffled delete_50 5 5.5900000006658956e-05
32 BST shuffled insert 1 0.004337399999712943
33 BST shuffled find_110 1 0.0001140000003942987
34 BST shuffled delete_50 1 7.019999975454994e-05
35 BST shuffled insert 2 0.004390000000057626
36 BST shuffled find_110 2 0.00011150000045745401
37 BST shuffled delete_50 2 6.52999997328152e-05
38 BST shuffled insert 3 0.004179900000053749
39 BST shuffled find_110 3 0.00010879999990720535
40 BST shuffled delete_50 3 6.070000017643906e-05
41 BST shuffled insert 4 0.004183999999440857
42 BST shuffled find_110 4 0.00010620000011840602
43 BST shuffled delete_50 4 9.539999973640079e-05
44 BST shuffled insert 5 0.004391600000417384
45 BST shuffled find_110 5 0.00010419999944133451
46 BST shuffled delete_50 5 6.630000007135095e-05
47 LinkedList sorted insert 1 0.19158669999978883
48 LinkedList sorted find_110 1 0.006112799999755225
49 LinkedList sorted delete_50 1 0.005895900000723486
50 LinkedList sorted insert 2 0.19256610000047658
51 LinkedList sorted find_110 2 0.006044900000233611
52 LinkedList sorted delete_50 2 0.002797499999360298
53 LinkedList sorted insert 3 0.19292270000005374
54 LinkedList sorted find_110 3 0.006467600000178209
55 LinkedList sorted delete_50 3 0.0028355000004012254
56 LinkedList sorted insert 4 0.19164809999983845
57 LinkedList sorted find_110 4 0.005796100000225124
58 LinkedList sorted delete_50 4 0.0030335999999806518
59 LinkedList sorted insert 5 0.1920518999995693
60 LinkedList sorted find_110 5 0.006033799999386247
61 LinkedList sorted delete_50 5 0.0028001999999105465
62 HashTable sorted insert 1 0.0036538000003929483
63 HashTable sorted find_110 1 0.0001173999999082298
64 HashTable sorted delete_50 1 5.810000038763974e-05
65 HashTable sorted insert 2 0.003586599999835016
66 HashTable sorted find_110 2 0.00011629999971773941
67 HashTable sorted delete_50 2 5.540000074688578e-05
68 HashTable sorted insert 3 0.00354940000033821
69 HashTable sorted find_110 3 0.00011549999999260763
70 HashTable sorted delete_50 3 5.520000013348181e-05
71 HashTable sorted insert 4 0.0036109000002397806
72 HashTable sorted find_110 4 0.00011329999961162684
73 HashTable sorted delete_50 4 5.6000000768108293e-05
74 HashTable sorted insert 5 0.003564399999959278
75 HashTable sorted find_110 5 0.00011339999946358148
76 HashTable sorted delete_50 5 5.480000072566327e-05
77 BST sorted insert 1 0.3111501000003045
78 BST sorted find_110 1 0.007159199999478005
79 BST sorted delete_50 1 0.004548900000372669
80 BST sorted insert 2 0.30066009999973176
81 BST sorted find_110 2 0.008023899999898276
82 BST sorted delete_50 2 0.004239900000357011
83 BST sorted insert 3 0.3020464000001084
84 BST sorted find_110 3 0.00876090000019758
85 BST sorted delete_50 3 0.004445499999746971
86 BST sorted insert 4 0.30183889999989333
87 BST sorted find_110 4 0.007328300000153831
88 BST sorted delete_50 4 0.005208799999309122
89 BST sorted insert 5 0.3034312999998292
90 BST sorted find_110 5 0.008107399999971676
91 BST sorted delete_50 5 0.004352900000412774

View File

@ -0,0 +1,19 @@
structure,mode,operation,mean_seconds
LinkedList,shuffled,insert,0.19510954000033962
LinkedList,shuffled,find_110,0.006131179999829328
LinkedList,shuffled,delete_50,0.0029453599998305437
HashTable,shuffled,insert,0.004042300000037358
HashTable,shuffled,find_110,0.00011881999980687397
HashTable,shuffled,delete_50,5.7859999833453914e-05
BST,shuffled,insert,0.004296579999936512
BST,shuffled,find_110,0.00010894000006373971
BST,shuffled,delete_50,7.157999989431119e-05
LinkedList,sorted,insert,0.19215509999994537
LinkedList,sorted,find_110,0.006091039999955683
LinkedList,sorted,delete_50,0.0034725400000752416
HashTable,sorted,insert,0.0035930200001530466
HashTable,sorted,find_110,0.00011517999973875703
HashTable,sorted,delete_50,5.590000055235578e-05
BST,sorted,insert,0.30382535999997345
BST,sorted,find_110,0.007875939999939874
BST,sorted,delete_50,0.004559200000039709
1 structure mode operation mean_seconds
2 LinkedList shuffled insert 0.19510954000033962
3 LinkedList shuffled find_110 0.006131179999829328
4 LinkedList shuffled delete_50 0.0029453599998305437
5 HashTable shuffled insert 0.004042300000037358
6 HashTable shuffled find_110 0.00011881999980687397
7 HashTable shuffled delete_50 5.7859999833453914e-05
8 BST shuffled insert 0.004296579999936512
9 BST shuffled find_110 0.00010894000006373971
10 BST shuffled delete_50 7.157999989431119e-05
11 LinkedList sorted insert 0.19215509999994537
12 LinkedList sorted find_110 0.006091039999955683
13 LinkedList sorted delete_50 0.0034725400000752416
14 HashTable sorted insert 0.0035930200001530466
15 HashTable sorted find_110 0.00011517999973875703
16 HashTable sorted delete_50 5.590000055235578e-05
17 BST sorted insert 0.30382535999997345
18 BST sorted find_110 0.007875939999939874
19 BST sorted delete_50 0.004559200000039709

View File

@ -0,0 +1,64 @@
import argparse
import csv
from pathlib import Path
from statistics import mean
from generate_mazes import generate_all
from maze_app import AStarStrategy, BFSStrategy, DFSStrategy, MazeSolver, TextFileMazeBuilder
STRATEGIES = (BFSStrategy, DFSStrategy, AStarStrategy)
def run_experiment(repeats=7, maze_dir="mazes", output_dir="docs/data"):
generate_all(maze_dir)
builder = TextFileMazeBuilder()
rows = []
for maze_path in sorted(Path(maze_dir).glob("*.txt")):
maze = builder.build_from_file(maze_path)
for strategy_type in STRATEGIES:
for run in range(1, repeats + 1):
stats = MazeSolver(maze, strategy_type()).solve()
rows.append({
"maze": maze_path.stem,
"strategy": stats.strategy,
"run": run,
"time_ms": stats.time_ms,
"visited_cells": stats.visited_cells,
"path_length": stats.path_length,
"path_found": bool(stats.path),
})
output = Path(output_dir)
output.mkdir(parents=True, exist_ok=True)
raw_path = output / "maze_results_raw.csv"
with raw_path.open("w", newline="", encoding="utf-8-sig") as file:
writer = csv.DictWriter(file, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
groups = {}
for row in rows:
groups.setdefault((row["maze"], row["strategy"]), []).append(row)
summary = []
for (maze_name, strategy), values in groups.items():
summary.append({
"maze": maze_name,
"strategy": strategy,
"mean_time_ms": mean(row["time_ms"] for row in values),
"mean_visited_cells": mean(row["visited_cells"] for row in values),
"path_length": values[0]["path_length"],
"path_found": values[0]["path_found"],
})
summary_path = output / "maze_results_summary.csv"
with summary_path.open("w", newline="", encoding="utf-8-sig") as file:
writer = csv.DictWriter(file, fieldnames=summary[0].keys())
writer.writeheader()
writer.writerows(summary)
return raw_path, summary_path
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Сравнение алгоритмов поиска пути")
parser.add_argument("--repeats", type=int, default=7)
args = parser.parse_args()
print("Результаты:", *run_experiment(args.repeats), sep="\n")

View File

@ -0,0 +1,52 @@
"""Генерация воспроизводимых тестовых лабиринтов."""
import random
from pathlib import Path
def obstacle_maze(width, height, wall_probability, seed):
rng = random.Random(seed)
grid = [["#" if x in (0, width - 1) or y in (0, height - 1) else " " for x in range(width)] for y in range(height)]
for y in range(1, height - 1):
for x in range(1, width - 1):
if rng.random() < wall_probability:
grid[y][x] = "#"
# Оставляем гарантированный путь по верхней и правой внутренним границам.
for x in range(1, width - 1):
grid[1][x] = " "
for y in range(1, height - 1):
grid[y][width - 2] = " "
grid[1][1], grid[height - 2][width - 2] = "S", "E"
return "\n".join("".join(row) for row in grid) + "\n"
def empty_maze(width=50, height=50):
return obstacle_maze(width, height, 0, 1)
def blocked_maze(width=30, height=30):
lines = empty_maze(width, height).splitlines()
grid = [list(line) for line in lines]
exit_y, exit_x = height - 2, width - 2
grid[exit_y - 1][exit_x] = "#"
grid[exit_y][exit_x - 1] = "#"
return "\n".join("".join(row) for row in grid) + "\n"
def generate_all(output_dir="mazes"):
output = Path(output_dir)
output.mkdir(parents=True, exist_ok=True)
maps = {
"small_10x10.txt": obstacle_maze(10, 10, 0.12, 10),
"medium_50x50.txt": obstacle_maze(50, 50, 0.28, 50),
"large_100x100.txt": obstacle_maze(100, 100, 0.32, 100),
"empty_50x50.txt": empty_maze(),
"no_path_30x30.txt": blocked_maze(),
}
for filename, content in maps.items():
(output / filename).write_text(content, encoding="utf-8")
return list(maps)
if __name__ == "__main__":
print("Созданы файлы:", ", ".join(generate_all()))

View File

@ -0,0 +1,24 @@
import argparse
from maze_app import AStarStrategy, BFSStrategy, ConsoleView, DFSStrategy, MazeSolver, TextFileMazeBuilder
STRATEGIES = {"bfs": BFSStrategy, "dfs": DFSStrategy, "astar": AStarStrategy}
def main():
parser = argparse.ArgumentParser(description="Поиск выхода из лабиринта")
parser.add_argument("maze", nargs="?", default="mazes/small_10x10.txt")
parser.add_argument("--algorithm", choices=STRATEGIES, default="bfs")
args = parser.parse_args()
maze = TextFileMazeBuilder().build_from_file(args.maze)
view = ConsoleView()
solver = MazeSolver(maze, STRATEGIES[args.algorithm]())
solver.attach(view)
stats = solver.solve()
print(view.render(maze, stats.path))
print(f"Время: {stats.time_ms:.4f} мс; посещено: {stats.visited_cells}; длина пути: {stats.path_length}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,246 @@
"""Модель лабиринта и алгоритмы поиска с паттернами Builder, Strategy, Observer."""
from abc import ABC, abstractmethod
from collections import deque
from dataclasses import dataclass, field
from heapq import heappop, heappush
from itertools import count
from pathlib import Path
from time import perf_counter
@dataclass(frozen=True)
class Cell:
x: int
y: int
is_wall: bool = False
is_start: bool = False
is_exit: bool = False
def is_passable(self):
return not self.is_wall
class Maze:
def __init__(self, cells):
if not cells or not cells[0]:
raise ValueError("Лабиринт не может быть пустым")
self.cells = cells
self.height = len(cells)
self.width = len(cells[0])
self.start = next((cell for row in cells for cell in row if cell.is_start), None)
self.exit = next((cell for row in cells for cell in row if cell.is_exit), None)
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def get_neighbors(self, cell):
neighbors = []
for dx, dy in ((0, -1), (1, 0), (0, 1), (-1, 0)):
neighbor = self.get_cell(cell.x + dx, cell.y + dy)
if neighbor is not None and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
SYMBOLS = {"#", " ", "S", "E"}
def build_from_file(self, filename):
lines = Path(filename).read_text(encoding="utf-8").splitlines()
if not lines or not lines[0] or any(len(line) != len(lines[0]) for line in lines):
raise ValueError("Строки лабиринта должны иметь одинаковую ненулевую длину")
unknown = {character for line in lines for character in line} - self.SYMBOLS
if unknown:
raise ValueError(f"Недопустимые символы: {sorted(unknown)}")
if sum(line.count("S") for line in lines) != 1 or sum(line.count("E") for line in lines) != 1:
raise ValueError("Лабиринт должен содержать ровно один старт S и один выход E")
cells = []
for y, line in enumerate(lines):
cells.append([
Cell(x, y, symbol == "#", symbol == "S", symbol == "E")
for x, symbol in enumerate(line)
])
return Maze(cells)
class PathFindingStrategy(ABC):
name = "Неизвестно"
def __init__(self):
self.visited_count = 0
@abstractmethod
def find_path(self, maze, start, exit_cell):
pass
@staticmethod
def restore_path(parent, start, exit_cell):
if exit_cell not in parent:
return []
path, current = [], exit_cell
while current is not None:
path.append(current)
current = parent[current]
path.reverse()
return path if path and path[0] == start else []
class BFSStrategy(PathFindingStrategy):
name = "BFS"
def find_path(self, maze, start, exit_cell):
queue = deque([start])
parent = {start: None}
visited = 0
while queue:
current = queue.popleft()
visited += 1
if current == exit_cell:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in parent:
parent[neighbor] = current
queue.append(neighbor)
self.visited_count = visited
return self.restore_path(parent, start, exit_cell)
class DFSStrategy(PathFindingStrategy):
name = "DFS"
def find_path(self, maze, start, exit_cell):
stack = [start]
parent = {start: None}
visited = 0
while stack:
current = stack.pop()
visited += 1
if current == exit_cell:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in parent:
parent[neighbor] = current
stack.append(neighbor)
self.visited_count = visited
return self.restore_path(parent, start, exit_cell)
class AStarStrategy(PathFindingStrategy):
name = "A*"
@staticmethod
def heuristic(first, second):
return abs(first.x - second.x) + abs(first.y - second.y)
def find_path(self, maze, start, exit_cell):
order = count()
queue = [(self.heuristic(start, exit_cell), next(order), start)]
distance = {start: 0}
parent = {start: None}
closed = set()
while queue:
_, _, current = heappop(queue)
if current in closed:
continue
closed.add(current)
if current == exit_cell:
break
for neighbor in maze.get_neighbors(current):
new_distance = distance[current] + 1
if new_distance < distance.get(neighbor, float("inf")):
distance[neighbor] = new_distance
parent[neighbor] = current
priority = new_distance + self.heuristic(neighbor, exit_cell)
heappush(queue, (priority, next(order), neighbor))
self.visited_count = len(closed)
return self.restore_path(parent, start, exit_cell)
@dataclass
class SearchStats:
strategy: str
time_ms: float
visited_cells: int
path_length: int
path: list = field(repr=False)
class Observer(ABC):
@abstractmethod
def update(self, event):
pass
class ConsoleView(Observer):
def __init__(self, verbose=True):
self.verbose = verbose
self.events = []
def update(self, event):
self.events.append(event)
if self.verbose:
print(event["message"])
def render(self, maze, path=None):
path_cells = set(path or [])
rows = []
for row in maze.cells:
symbols = []
for cell in row:
if cell.is_start:
symbols.append("S")
elif cell.is_exit:
symbols.append("E")
elif cell.is_wall:
symbols.append("#")
elif cell in path_cells:
symbols.append(".")
else:
symbols.append(" ")
rows.append("".join(symbols))
return "\n".join(rows)
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy):
self.strategy = strategy
def attach(self, observer):
if observer not in self.observers:
self.observers.append(observer)
def notify(self, event):
for observer in self.observers:
observer.update(event)
def solve(self):
self.notify({"type": "search_started", "message": f"Запущен алгоритм {self.strategy.name}"})
started = perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
elapsed_ms = max((perf_counter() - started) * 1000, 1e-9)
stats = SearchStats(
strategy=self.strategy.name,
time_ms=elapsed_ms,
visited_cells=self.strategy.visited_count,
path_length=max(len(path) - 1, 0),
path=path,
)
event_type = "path_found" if path else "path_not_found"
message = f"{self.strategy.name}: путь длиной {stats.path_length}" if path else f"{self.strategy.name}: путь не найден"
self.notify({"type": event_type, "message": message, "stats": stats})
return stats

View File

@ -0,0 +1,106 @@
maze,strategy,run,time_ms,visited_cells,path_length,path_found
empty_50x50,BFS,1,28.560300008393824,2304,94,True
empty_50x50,BFS,2,21.739099989645183,2304,94,True
empty_50x50,BFS,3,28.969400038477033,2304,94,True
empty_50x50,BFS,4,26.839600002858788,2304,94,True
empty_50x50,BFS,5,16.788399952929467,2304,94,True
empty_50x50,BFS,6,14.332599996123463,2304,94,True
empty_50x50,BFS,7,22.064500022679567,2304,94,True
empty_50x50,DFS,1,0.5307000246830285,95,94,True
empty_50x50,DFS,2,0.6820000126026571,95,94,True
empty_50x50,DFS,3,0.5209000082686543,95,94,True
empty_50x50,DFS,4,0.5133000086061656,95,94,True
empty_50x50,DFS,5,0.8089999901130795,95,94,True
empty_50x50,DFS,6,0.5200000014156103,95,94,True
empty_50x50,DFS,7,0.8933999924920499,95,94,True
empty_50x50,A*,1,68.07579996529967,2304,94,True
empty_50x50,A*,2,61.01420003687963,2304,94,True
empty_50x50,A*,3,52.171000046655536,2304,94,True
empty_50x50,A*,4,53.36979997809976,2304,94,True
empty_50x50,A*,5,64.77910000830889,2304,94,True
empty_50x50,A*,6,51.02490005083382,2304,94,True
empty_50x50,A*,7,70.88650000514463,2304,94,True
large_100x100,BFS,1,91.43029997358099,6409,194,True
large_100x100,BFS,2,98.55669998796657,6409,194,True
large_100x100,BFS,3,98.43990002991632,6409,194,True
large_100x100,BFS,4,51.18469998706132,6409,194,True
large_100x100,BFS,5,44.218000024557114,6409,194,True
large_100x100,BFS,6,28.500600019469857,6409,194,True
large_100x100,BFS,7,60.57189998682588,6409,194,True
large_100x100,DFS,1,25.9578000404872,1561,762,True
large_100x100,DFS,2,18.451499985530972,1561,762,True
large_100x100,DFS,3,18.506099993828684,1561,762,True
large_100x100,DFS,4,14.582899981178343,1561,762,True
large_100x100,DFS,5,10.724799998570234,1561,762,True
large_100x100,DFS,6,17.07870000973344,1561,762,True
large_100x100,DFS,7,19.463400007225573,1561,762,True
large_100x100,A*,1,38.1690000067465,1997,194,True
large_100x100,A*,2,30.891000002156943,1997,194,True
large_100x100,A*,3,23.280899971723557,1997,194,True
large_100x100,A*,4,26.04500000597909,1997,194,True
large_100x100,A*,5,27.82869996735826,1997,194,True
large_100x100,A*,6,26.94000001065433,1997,194,True
large_100x100,A*,7,28.832400043029338,1997,194,True
medium_50x50,BFS,1,6.693199975416064,1700,94,True
medium_50x50,BFS,2,6.831499980762601,1700,94,True
medium_50x50,BFS,3,8.055799989961088,1700,94,True
medium_50x50,BFS,4,7.26869999198243,1700,94,True
medium_50x50,BFS,5,11.507100018206984,1700,94,True
medium_50x50,BFS,6,7.060799980536103,1700,94,True
medium_50x50,BFS,7,7.459699991159141,1700,94,True
medium_50x50,DFS,1,3.0005000298842788,408,266,True
medium_50x50,DFS,2,1.8214000156149268,408,266,True
medium_50x50,DFS,3,2.6385000091977417,408,266,True
medium_50x50,DFS,4,1.7798999906517565,408,266,True
medium_50x50,DFS,5,1.7468000296503305,408,266,True
medium_50x50,DFS,6,1.7085999716073275,408,266,True
medium_50x50,DFS,7,1.7067999579012394,408,266,True
medium_50x50,A*,1,8.779299969319254,942,94,True
medium_50x50,A*,2,9.8794000223279,942,94,True
medium_50x50,A*,3,9.167099953629076,942,94,True
medium_50x50,A*,4,16.693999990820885,942,94,True
medium_50x50,A*,5,12.841499992646277,942,94,True
medium_50x50,A*,6,23.23459996841848,942,94,True
medium_50x50,A*,7,18.193199997767806,942,94,True
no_path_30x30,BFS,1,3.5794000141322613,781,0,False
no_path_30x30,BFS,2,3.4125999663956463,781,0,False
no_path_30x30,BFS,3,3.811199974734336,781,0,False
no_path_30x30,BFS,4,3.524600004311651,781,0,False
no_path_30x30,BFS,5,3.535500029101968,781,0,False
no_path_30x30,BFS,6,3.6441999836824834,781,0,False
no_path_30x30,BFS,7,3.4087999956682324,781,0,False
no_path_30x30,DFS,1,3.3659999608062208,781,0,False
no_path_30x30,DFS,2,3.659199981484562,781,0,False
no_path_30x30,DFS,3,3.4770999918691814,781,0,False
no_path_30x30,DFS,4,3.4067999804392457,781,0,False
no_path_30x30,DFS,5,3.334700013510883,781,0,False
no_path_30x30,DFS,6,3.7082999479025602,781,0,False
no_path_30x30,DFS,7,5.642200005240738,781,0,False
no_path_30x30,A*,1,7.9901000135578215,781,0,False
no_path_30x30,A*,2,8.304700022563338,781,0,False
no_path_30x30,A*,3,11.212200042791665,781,0,False
no_path_30x30,A*,4,14.160500024445355,781,0,False
no_path_30x30,A*,5,9.939100011251867,781,0,False
no_path_30x30,A*,6,8.455800008960068,781,0,False
no_path_30x30,A*,7,7.638900016900152,781,0,False
small_10x10,BFS,1,0.18330005696043372,58,14,True
small_10x10,BFS,2,0.14630000805482268,58,14,True
small_10x10,BFS,3,0.18289999570697546,58,14,True
small_10x10,BFS,4,0.14349998673424125,58,14,True
small_10x10,BFS,5,0.14270003885030746,58,14,True
small_10x10,BFS,6,0.1477000187151134,58,14,True
small_10x10,BFS,7,0.14270003885030746,58,14,True
small_10x10,DFS,1,0.042700034100562334,15,14,True
small_10x10,DFS,2,0.04120002267882228,15,14,True
small_10x10,DFS,3,0.041000021155923605,15,14,True
small_10x10,DFS,4,0.04099996294826269,15,14,True
small_10x10,DFS,5,0.04079996142536402,15,14,True
small_10x10,DFS,6,0.04060001811012626,15,14,True
small_10x10,DFS,7,0.04060001811012626,15,14,True
small_10x10,A*,1,0.30999997397884727,58,14,True
small_10x10,A*,2,0.30529999639838934,58,14,True
small_10x10,A*,3,0.325299974065274,58,14,True
small_10x10,A*,4,0.31670002499595284,58,14,True
small_10x10,A*,5,0.32130000181496143,58,14,True
small_10x10,A*,6,0.3467000206001103,58,14,True
small_10x10,A*,7,0.33109996002167463,58,14,True
1 maze strategy run time_ms visited_cells path_length path_found
2 empty_50x50 BFS 1 28.560300008393824 2304 94 True
3 empty_50x50 BFS 2 21.739099989645183 2304 94 True
4 empty_50x50 BFS 3 28.969400038477033 2304 94 True
5 empty_50x50 BFS 4 26.839600002858788 2304 94 True
6 empty_50x50 BFS 5 16.788399952929467 2304 94 True
7 empty_50x50 BFS 6 14.332599996123463 2304 94 True
8 empty_50x50 BFS 7 22.064500022679567 2304 94 True
9 empty_50x50 DFS 1 0.5307000246830285 95 94 True
10 empty_50x50 DFS 2 0.6820000126026571 95 94 True
11 empty_50x50 DFS 3 0.5209000082686543 95 94 True
12 empty_50x50 DFS 4 0.5133000086061656 95 94 True
13 empty_50x50 DFS 5 0.8089999901130795 95 94 True
14 empty_50x50 DFS 6 0.5200000014156103 95 94 True
15 empty_50x50 DFS 7 0.8933999924920499 95 94 True
16 empty_50x50 A* 1 68.07579996529967 2304 94 True
17 empty_50x50 A* 2 61.01420003687963 2304 94 True
18 empty_50x50 A* 3 52.171000046655536 2304 94 True
19 empty_50x50 A* 4 53.36979997809976 2304 94 True
20 empty_50x50 A* 5 64.77910000830889 2304 94 True
21 empty_50x50 A* 6 51.02490005083382 2304 94 True
22 empty_50x50 A* 7 70.88650000514463 2304 94 True
23 large_100x100 BFS 1 91.43029997358099 6409 194 True
24 large_100x100 BFS 2 98.55669998796657 6409 194 True
25 large_100x100 BFS 3 98.43990002991632 6409 194 True
26 large_100x100 BFS 4 51.18469998706132 6409 194 True
27 large_100x100 BFS 5 44.218000024557114 6409 194 True
28 large_100x100 BFS 6 28.500600019469857 6409 194 True
29 large_100x100 BFS 7 60.57189998682588 6409 194 True
30 large_100x100 DFS 1 25.9578000404872 1561 762 True
31 large_100x100 DFS 2 18.451499985530972 1561 762 True
32 large_100x100 DFS 3 18.506099993828684 1561 762 True
33 large_100x100 DFS 4 14.582899981178343 1561 762 True
34 large_100x100 DFS 5 10.724799998570234 1561 762 True
35 large_100x100 DFS 6 17.07870000973344 1561 762 True
36 large_100x100 DFS 7 19.463400007225573 1561 762 True
37 large_100x100 A* 1 38.1690000067465 1997 194 True
38 large_100x100 A* 2 30.891000002156943 1997 194 True
39 large_100x100 A* 3 23.280899971723557 1997 194 True
40 large_100x100 A* 4 26.04500000597909 1997 194 True
41 large_100x100 A* 5 27.82869996735826 1997 194 True
42 large_100x100 A* 6 26.94000001065433 1997 194 True
43 large_100x100 A* 7 28.832400043029338 1997 194 True
44 medium_50x50 BFS 1 6.693199975416064 1700 94 True
45 medium_50x50 BFS 2 6.831499980762601 1700 94 True
46 medium_50x50 BFS 3 8.055799989961088 1700 94 True
47 medium_50x50 BFS 4 7.26869999198243 1700 94 True
48 medium_50x50 BFS 5 11.507100018206984 1700 94 True
49 medium_50x50 BFS 6 7.060799980536103 1700 94 True
50 medium_50x50 BFS 7 7.459699991159141 1700 94 True
51 medium_50x50 DFS 1 3.0005000298842788 408 266 True
52 medium_50x50 DFS 2 1.8214000156149268 408 266 True
53 medium_50x50 DFS 3 2.6385000091977417 408 266 True
54 medium_50x50 DFS 4 1.7798999906517565 408 266 True
55 medium_50x50 DFS 5 1.7468000296503305 408 266 True
56 medium_50x50 DFS 6 1.7085999716073275 408 266 True
57 medium_50x50 DFS 7 1.7067999579012394 408 266 True
58 medium_50x50 A* 1 8.779299969319254 942 94 True
59 medium_50x50 A* 2 9.8794000223279 942 94 True
60 medium_50x50 A* 3 9.167099953629076 942 94 True
61 medium_50x50 A* 4 16.693999990820885 942 94 True
62 medium_50x50 A* 5 12.841499992646277 942 94 True
63 medium_50x50 A* 6 23.23459996841848 942 94 True
64 medium_50x50 A* 7 18.193199997767806 942 94 True
65 no_path_30x30 BFS 1 3.5794000141322613 781 0 False
66 no_path_30x30 BFS 2 3.4125999663956463 781 0 False
67 no_path_30x30 BFS 3 3.811199974734336 781 0 False
68 no_path_30x30 BFS 4 3.524600004311651 781 0 False
69 no_path_30x30 BFS 5 3.535500029101968 781 0 False
70 no_path_30x30 BFS 6 3.6441999836824834 781 0 False
71 no_path_30x30 BFS 7 3.4087999956682324 781 0 False
72 no_path_30x30 DFS 1 3.3659999608062208 781 0 False
73 no_path_30x30 DFS 2 3.659199981484562 781 0 False
74 no_path_30x30 DFS 3 3.4770999918691814 781 0 False
75 no_path_30x30 DFS 4 3.4067999804392457 781 0 False
76 no_path_30x30 DFS 5 3.334700013510883 781 0 False
77 no_path_30x30 DFS 6 3.7082999479025602 781 0 False
78 no_path_30x30 DFS 7 5.642200005240738 781 0 False
79 no_path_30x30 A* 1 7.9901000135578215 781 0 False
80 no_path_30x30 A* 2 8.304700022563338 781 0 False
81 no_path_30x30 A* 3 11.212200042791665 781 0 False
82 no_path_30x30 A* 4 14.160500024445355 781 0 False
83 no_path_30x30 A* 5 9.939100011251867 781 0 False
84 no_path_30x30 A* 6 8.455800008960068 781 0 False
85 no_path_30x30 A* 7 7.638900016900152 781 0 False
86 small_10x10 BFS 1 0.18330005696043372 58 14 True
87 small_10x10 BFS 2 0.14630000805482268 58 14 True
88 small_10x10 BFS 3 0.18289999570697546 58 14 True
89 small_10x10 BFS 4 0.14349998673424125 58 14 True
90 small_10x10 BFS 5 0.14270003885030746 58 14 True
91 small_10x10 BFS 6 0.1477000187151134 58 14 True
92 small_10x10 BFS 7 0.14270003885030746 58 14 True
93 small_10x10 DFS 1 0.042700034100562334 15 14 True
94 small_10x10 DFS 2 0.04120002267882228 15 14 True
95 small_10x10 DFS 3 0.041000021155923605 15 14 True
96 small_10x10 DFS 4 0.04099996294826269 15 14 True
97 small_10x10 DFS 5 0.04079996142536402 15 14 True
98 small_10x10 DFS 6 0.04060001811012626 15 14 True
99 small_10x10 DFS 7 0.04060001811012626 15 14 True
100 small_10x10 A* 1 0.30999997397884727 58 14 True
101 small_10x10 A* 2 0.30529999639838934 58 14 True
102 small_10x10 A* 3 0.325299974065274 58 14 True
103 small_10x10 A* 4 0.31670002499595284 58 14 True
104 small_10x10 A* 5 0.32130000181496143 58 14 True
105 small_10x10 A* 6 0.3467000206001103 58 14 True
106 small_10x10 A* 7 0.33109996002167463 58 14 True

View File

@ -0,0 +1,16 @@
maze,strategy,mean_time_ms,mean_visited_cells,path_length,path_found
empty_50x50,BFS,22.75627143015819,2304,94,True
empty_50x50,DFS,0.6384714340258922,95,94,True
empty_50x50,A*,60.188757155888844,2304,94,True
large_100x100,BFS,67.55744285848257,6409,194,True
large_100x100,DFS,17.82360000236492,1561,762,True
large_100x100,A*,28.85528571537829,1997,194,True
medium_50x50,BFS,7.83954284686063,1700,94,True
medium_50x50,DFS,2.0575000006439432,408,266,True
medium_50x50,A*,14.112728556418526,942,94,True
no_path_30x30,BFS,3.559471424003797,781,0,False
no_path_30x30,DFS,3.799185697321913,781,0,False
no_path_30x30,A*,9.671614305781466,781,0,False
small_10x10,BFS,0.1555857348388859,58,14,True
small_10x10,DFS,0.041128576932741065,15,14,True
small_10x10,A*,0.3223428502678871,58,14,True
1 maze strategy mean_time_ms mean_visited_cells path_length path_found
2 empty_50x50 BFS 22.75627143015819 2304 94 True
3 empty_50x50 DFS 0.6384714340258922 95 94 True
4 empty_50x50 A* 60.188757155888844 2304 94 True
5 large_100x100 BFS 67.55744285848257 6409 194 True
6 large_100x100 DFS 17.82360000236492 1561 762 True
7 large_100x100 A* 28.85528571537829 1997 194 True
8 medium_50x50 BFS 7.83954284686063 1700 94 True
9 medium_50x50 DFS 2.0575000006439432 408 266 True
10 medium_50x50 A* 14.112728556418526 942 94 True
11 no_path_30x30 BFS 3.559471424003797 781 0 False
12 no_path_30x30 DFS 3.799185697321913 781 0 False
13 no_path_30x30 A* 9.671614305781466 781 0 False
14 small_10x10 BFS 0.1555857348388859 58 14 True
15 small_10x10 DFS 0.041128576932741065 15 14 True
16 small_10x10 A* 0.3223428502678871 58 14 True

View File

@ -0,0 +1,35 @@
import csv
from pathlib import Path
import matplotlib.pyplot as plt
def create_plot(csv_path="docs/data/maze_results_summary.csv", output_path="docs/data/maze_performance.png"):
with Path(csv_path).open(encoding="utf-8-sig") as file:
rows = list(csv.DictReader(file))
mazes = sorted({row["maze"] for row in rows})
strategies = ["BFS", "DFS", "A*"]
colors = {"BFS": "#4472C4", "DFS": "#ED7D31", "A*": "#70AD47"}
figure, axes = plt.subplots(2, 1, figsize=(12, 9))
positions = range(len(mazes))
width = 0.24
for index, strategy in enumerate(strategies):
selected = [next(row for row in rows if row["maze"] == maze and row["strategy"] == strategy) for maze in mazes]
offsets = [position + (index - 1) * width for position in positions]
axes[0].bar(offsets, [float(row["mean_time_ms"]) for row in selected], width, label=strategy, color=colors[strategy])
axes[1].bar(offsets, [float(row["mean_visited_cells"]) for row in selected], width, label=strategy, color=colors[strategy])
for axis, ylabel, title in zip(axes, ["Время, мс", "Посещено клеток"], ["Среднее время поиска", "Объём исследования лабиринта"]):
axis.set_xticks(list(positions), mazes, rotation=20, ha="right")
axis.set_ylabel(ylabel)
axis.set_title(title)
axis.grid(axis="y", alpha=0.25)
axis.legend()
figure.suptitle("Сравнение стратегий поиска (средние значения)")
figure.tight_layout()
figure.savefig(output_path, dpi=180)
plt.close(figure)
return Path(output_path)
if __name__ == "__main__":
print(f"График сохранён: {create_plot()}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.