task 1, 2

This commit is contained in:
Yaroslav 2026-05-30 22:27:37 +03:00
parent 8124ef4551
commit 9f14ed95db
69 changed files with 1724 additions and 0 deletions

0
petryainiyas/426.md Normal file
View File

118
petryainiyas/task1/bst.py Normal file
View File

@ -0,0 +1,118 @@
from typing import Any, Dict, List, Optional
Node = Dict[str, Any]
def _make_node(name: str, phone: str) -> Node:
return {"name": name, "phone": phone, "left": None, "right": None}
def bst_insert(root: Optional[Node], name: str, phone: str) -> Node:
new_node = _make_node(name, phone)
if root is None:
return new_node
current = root
parent = None
while current is not None:
parent = current
if name < current["name"]:
current = current["left"]
elif name > current["name"]:
current = current["right"]
else:
current["phone"] = phone
return root
if name < parent["name"]:
parent["left"] = new_node
else:
parent["right"] = new_node
return root
def bst_find(root: Optional[Node], name: str) -> Optional[str]:
current = root
while current is not None:
if name < current["name"]:
current = current["left"]
elif name > current["name"]:
current = current["right"]
else:
return current["phone"]
return None
def _find_min_node(node: Node) -> Node:
current = node
while current["left"] is not None:
current = current["left"]
return current
def bst_delete(root: Optional[Node], name: str) -> Optional[Node]:
if root is None:
return None
parent = None
current = root
while current is not None and current["name"] != name:
parent = current
if name < current["name"]:
current = current["left"]
else:
current = current["right"]
if current is None:
return root
if current["left"] is None or current["right"] is None:
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
succ_parent = current
successor = current["right"]
while successor["left"] is not None:
succ_parent = successor
successor = successor["left"]
current["name"] = successor["name"]
current["phone"] = successor["phone"]
successor_child = successor["right"]
if succ_parent["left"] is successor:
succ_parent["left"] = successor_child
else:
succ_parent["right"] = successor_child
return root
def bst_list_all(root: Optional[Node]) -> List[Dict[str, str]]:
result: List[Dict[str, str]] = []
stack: List[Node] = []
current = root
while current is not None or stack:
while current is not None:
stack.append(current)
current = current["left"]
current = stack.pop()
result.append({"name": current["name"], "phone": current["phone"]})
current = current["right"]
return result

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,109 @@
Структура,Режим,Операция,Замер,Время (сек)
LinkedList,случайный,insert,1,4.2622492010
LinkedList,случайный,find,1,0.0314994130
LinkedList,случайный,delete,1,0.0149069000
LinkedList,случайный,insert,2,4.0154580330
LinkedList,случайный,find,2,0.0393284500
LinkedList,случайный,delete,2,0.0210732100
LinkedList,случайный,insert,3,4.0436019780
LinkedList,случайный,find,3,0.0344933660
LinkedList,случайный,delete,3,0.0152639850
LinkedList,случайный,insert,4,3.7182993220
LinkedList,случайный,find,4,0.0327698850
LinkedList,случайный,delete,4,0.0149959540
LinkedList,случайный,insert,5,3.7082228200
LinkedList,случайный,find,5,0.0303762490
LinkedList,случайный,delete,5,0.0141406560
LinkedList,случайный,insert,среднее,3.9495662708
LinkedList,случайный,find,среднее,0.0336934726
LinkedList,случайный,delete,среднее,0.0160761410
HashTable,случайный,insert,1,0.2059865770
HashTable,случайный,find,1,0.0014966100
HashTable,случайный,delete,1,0.0006891700
HashTable,случайный,insert,2,0.2024331460
HashTable,случайный,find,2,0.0015934880
HashTable,случайный,delete,2,0.0007212620
HashTable,случайный,insert,3,0.2126128040
HashTable,случайный,find,3,0.0016566220
HashTable,случайный,delete,3,0.0008358420
HashTable,случайный,insert,4,0.2157934910
HashTable,случайный,find,4,0.0015542810
HashTable,случайный,delete,4,0.0007269120
HashTable,случайный,insert,5,0.2079924580
HashTable,случайный,find,5,0.0013696990
HashTable,случайный,delete,5,0.0006616050
HashTable,случайный,insert,среднее,0.2089636952
HashTable,случайный,find,среднее,0.0015341400
HashTable,случайный,delete,среднее,0.0007269582
BST,случайный,insert,1,0.0166981280
BST,случайный,find,1,0.0001569360
BST,случайный,delete,1,0.0000917280
BST,случайный,insert,2,0.0184119040
BST,случайный,find,2,0.0001517110
BST,случайный,delete,2,0.0001163770
BST,случайный,insert,3,0.0174662270
BST,случайный,find,3,0.0001582930
BST,случайный,delete,3,0.0000892660
BST,случайный,insert,4,0.0191369100
BST,случайный,find,4,0.0002087170
BST,случайный,delete,4,0.0001067050
BST,случайный,insert,5,0.0184276900
BST,случайный,find,5,0.0002767720
BST,случайный,delete,5,0.0001067660
BST,случайный,insert,среднее,0.0180281718
BST,случайный,find,среднее,0.0001904858
BST,случайный,delete,среднее,0.0001021684
LinkedList,отсортированный,insert,1,2.9875078340
LinkedList,отсортированный,find,1,0.0237300610
LinkedList,отсортированный,delete,1,0.0111698260
LinkedList,отсортированный,insert,2,3.0573987940
LinkedList,отсортированный,find,2,0.0243270360
LinkedList,отсортированный,delete,2,0.0115366030
LinkedList,отсортированный,insert,3,2.9641987260
LinkedList,отсортированный,find,3,0.0236313330
LinkedList,отсортированный,delete,3,0.0112848510
LinkedList,отсортированный,insert,4,3.0345914950
LinkedList,отсортированный,find,4,0.0240271220
LinkedList,отсортированный,delete,4,0.0112117310
LinkedList,отсортированный,insert,5,2.9481954700
LinkedList,отсортированный,find,5,0.0239006100
LinkedList,отсортированный,delete,5,0.0110857710
LinkedList,отсортированный,insert,среднее,2.9983784638
LinkedList,отсортированный,find,среднее,0.0239232324
LinkedList,отсортированный,delete,среднее,0.0112577564
HashTable,отсортированный,insert,1,0.1997087560
HashTable,отсортированный,find,1,0.0017550400
HashTable,отсортированный,delete,1,0.0008407980
HashTable,отсортированный,insert,2,0.1968675190
HashTable,отсортированный,find,2,0.0019886760
HashTable,отсортированный,delete,2,0.0008920910
HashTable,отсортированный,insert,3,0.1907563580
HashTable,отсортированный,find,3,0.0018447440
HashTable,отсортированный,delete,3,0.0008684640
HashTable,отсортированный,insert,4,0.2625327630
HashTable,отсортированный,find,4,0.0016053140
HashTable,отсортированный,delete,4,0.0008098670
HashTable,отсортированный,insert,5,0.1936840590
HashTable,отсортированный,find,5,0.0019015160
HashTable,отсортированный,delete,5,0.0009053780
HashTable,отсортированный,insert,среднее,0.2087098910
HashTable,отсортированный,find,среднее,0.0018190580
HashTable,отсортированный,delete,среднее,0.0008633196
BST,отсортированный,insert,1,4.2195800190
BST,отсортированный,find,1,0.0389314570
BST,отсортированный,delete,1,0.0190308920
BST,отсортированный,insert,2,4.1356184250
BST,отсортированный,find,2,0.0383339310
BST,отсортированный,delete,2,0.0194247740
BST,отсортированный,insert,3,4.1204731890
BST,отсортированный,find,3,0.0388593320
BST,отсортированный,delete,3,0.0215428460
BST,отсортированный,insert,4,4.2120902370
BST,отсортированный,find,4,0.0378190250
BST,отсортированный,delete,4,0.0188528460
BST,отсортированный,insert,5,4.1304951260
BST,отсортированный,find,5,0.0359927840
BST,отсортированный,delete,5,0.0179617110
BST,отсортированный,insert,среднее,4.1636513992
BST,отсортированный,find,среднее,0.0379873058
BST,отсортированный,delete,среднее,0.0193626138
1 Структура Режим Операция Замер Время (сек)
2 LinkedList случайный insert 1 4.2622492010
3 LinkedList случайный find 1 0.0314994130
4 LinkedList случайный delete 1 0.0149069000
5 LinkedList случайный insert 2 4.0154580330
6 LinkedList случайный find 2 0.0393284500
7 LinkedList случайный delete 2 0.0210732100
8 LinkedList случайный insert 3 4.0436019780
9 LinkedList случайный find 3 0.0344933660
10 LinkedList случайный delete 3 0.0152639850
11 LinkedList случайный insert 4 3.7182993220
12 LinkedList случайный find 4 0.0327698850
13 LinkedList случайный delete 4 0.0149959540
14 LinkedList случайный insert 5 3.7082228200
15 LinkedList случайный find 5 0.0303762490
16 LinkedList случайный delete 5 0.0141406560
17 LinkedList случайный insert среднее 3.9495662708
18 LinkedList случайный find среднее 0.0336934726
19 LinkedList случайный delete среднее 0.0160761410
20 HashTable случайный insert 1 0.2059865770
21 HashTable случайный find 1 0.0014966100
22 HashTable случайный delete 1 0.0006891700
23 HashTable случайный insert 2 0.2024331460
24 HashTable случайный find 2 0.0015934880
25 HashTable случайный delete 2 0.0007212620
26 HashTable случайный insert 3 0.2126128040
27 HashTable случайный find 3 0.0016566220
28 HashTable случайный delete 3 0.0008358420
29 HashTable случайный insert 4 0.2157934910
30 HashTable случайный find 4 0.0015542810
31 HashTable случайный delete 4 0.0007269120
32 HashTable случайный insert 5 0.2079924580
33 HashTable случайный find 5 0.0013696990
34 HashTable случайный delete 5 0.0006616050
35 HashTable случайный insert среднее 0.2089636952
36 HashTable случайный find среднее 0.0015341400
37 HashTable случайный delete среднее 0.0007269582
38 BST случайный insert 1 0.0166981280
39 BST случайный find 1 0.0001569360
40 BST случайный delete 1 0.0000917280
41 BST случайный insert 2 0.0184119040
42 BST случайный find 2 0.0001517110
43 BST случайный delete 2 0.0001163770
44 BST случайный insert 3 0.0174662270
45 BST случайный find 3 0.0001582930
46 BST случайный delete 3 0.0000892660
47 BST случайный insert 4 0.0191369100
48 BST случайный find 4 0.0002087170
49 BST случайный delete 4 0.0001067050
50 BST случайный insert 5 0.0184276900
51 BST случайный find 5 0.0002767720
52 BST случайный delete 5 0.0001067660
53 BST случайный insert среднее 0.0180281718
54 BST случайный find среднее 0.0001904858
55 BST случайный delete среднее 0.0001021684
56 LinkedList отсортированный insert 1 2.9875078340
57 LinkedList отсортированный find 1 0.0237300610
58 LinkedList отсортированный delete 1 0.0111698260
59 LinkedList отсортированный insert 2 3.0573987940
60 LinkedList отсортированный find 2 0.0243270360
61 LinkedList отсортированный delete 2 0.0115366030
62 LinkedList отсортированный insert 3 2.9641987260
63 LinkedList отсортированный find 3 0.0236313330
64 LinkedList отсортированный delete 3 0.0112848510
65 LinkedList отсортированный insert 4 3.0345914950
66 LinkedList отсортированный find 4 0.0240271220
67 LinkedList отсортированный delete 4 0.0112117310
68 LinkedList отсортированный insert 5 2.9481954700
69 LinkedList отсортированный find 5 0.0239006100
70 LinkedList отсортированный delete 5 0.0110857710
71 LinkedList отсортированный insert среднее 2.9983784638
72 LinkedList отсортированный find среднее 0.0239232324
73 LinkedList отсортированный delete среднее 0.0112577564
74 HashTable отсортированный insert 1 0.1997087560
75 HashTable отсортированный find 1 0.0017550400
76 HashTable отсортированный delete 1 0.0008407980
77 HashTable отсортированный insert 2 0.1968675190
78 HashTable отсортированный find 2 0.0019886760
79 HashTable отсортированный delete 2 0.0008920910
80 HashTable отсортированный insert 3 0.1907563580
81 HashTable отсортированный find 3 0.0018447440
82 HashTable отсортированный delete 3 0.0008684640
83 HashTable отсортированный insert 4 0.2625327630
84 HashTable отсортированный find 4 0.0016053140
85 HashTable отсортированный delete 4 0.0008098670
86 HashTable отсортированный insert 5 0.1936840590
87 HashTable отсортированный find 5 0.0019015160
88 HashTable отсортированный delete 5 0.0009053780
89 HashTable отсортированный insert среднее 0.2087098910
90 HashTable отсортированный find среднее 0.0018190580
91 HashTable отсортированный delete среднее 0.0008633196
92 BST отсортированный insert 1 4.2195800190
93 BST отсортированный find 1 0.0389314570
94 BST отсортированный delete 1 0.0190308920
95 BST отсортированный insert 2 4.1356184250
96 BST отсортированный find 2 0.0383339310
97 BST отсортированный delete 2 0.0194247740
98 BST отсортированный insert 3 4.1204731890
99 BST отсортированный find 3 0.0388593320
100 BST отсортированный delete 3 0.0215428460
101 BST отсортированный insert 4 4.2120902370
102 BST отсортированный find 4 0.0378190250
103 BST отсортированный delete 4 0.0188528460
104 BST отсортированный insert 5 4.1304951260
105 BST отсортированный find 5 0.0359927840
106 BST отсортированный delete 5 0.0179617110
107 BST отсортированный insert среднее 4.1636513992
108 BST отсортированный find среднее 0.0379873058
109 BST отсортированный delete среднее 0.0193626138

Binary file not shown.

View File

@ -0,0 +1,172 @@
from __future__ import annotations
import csv
import random
import time
from pathlib import Path
from typing import Dict, List, Tuple
from linked_list import ll_insert, ll_find, ll_delete
from hash_table import ht_insert, ht_find, ht_delete
from bst import bst_insert, bst_find, bst_delete
from utils import generate_records, prepare_records_variants
Record = Tuple[str, str]
def make_missing_names(count: int = 10) -> List[str]:
return [f"None_{i}" for i in range(count)]
def pick_existing_names(records: List[Record], count: int, seed: int = 42) -> List[str]:
rng = random.Random(seed)
unique_names = list(dict.fromkeys(name for name, _ in records))
if len(unique_names) < count:
raise ValueError(f"Not enough unique names: need {count}, got {len(unique_names)}")
return rng.sample(unique_names, count)
def pick_delete_names(records: List[Record], count: int = 50, seed: int = 43) -> List[str]:
rng = random.Random(seed)
unique_names = list(dict.fromkeys(name for name, _ in records))
if len(unique_names) < count:
raise ValueError(f"Not enough unique names: need {count}, got {len(unique_names)}")
return rng.sample(unique_names, count)
def build_structure(structure_name: str, records: List[Record], buckets_count: int = 2048):
if structure_name == "linked_list":
structure = None
for name, phone in records:
structure = ll_insert(structure, name, phone)
return structure
if structure_name == "hash_table":
buckets = [None] * buckets_count
for name, phone in records:
buckets = ht_insert(buckets, name, phone)
return buckets
if structure_name == "bst":
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
return root
raise ValueError(f"Unknown structure: {structure_name}")
def do_find(structure_name: str, structure: object, existing_names: List[str], missing_names: List[str]) -> None:
if structure_name == "linked_list":
for name in existing_names:
ll_find(structure, name)
for name in missing_names:
ll_find(structure, name)
return
if structure_name == "hash_table":
for name in existing_names:
ht_find(structure, name)
for name in missing_names:
ht_find(structure, name)
return
if structure_name == "bst":
for name in existing_names:
bst_find(structure, name)
for name in missing_names:
bst_find(structure, name)
return
raise ValueError(f"Unknown structure: {structure_name}")
def do_delete(structure_name: str, structure: object, delete_names: List[str]):
if structure_name == "linked_list":
for name in delete_names:
structure = ll_delete(structure, name)
return structure
if structure_name == "hash_table":
for name in delete_names:
structure = ht_delete(structure, name)
return structure
if structure_name == "bst":
for name in delete_names:
structure = bst_delete(structure, name)
return structure
raise ValueError(f"Unknown structure: {structure_name}")
def measure_once(structure_name: str, records: List[Record], buckets_count: int = 2048) -> Dict[str, float]:
existing_names = pick_existing_names(records, 100, seed=42)
missing_names = make_missing_names(10)
delete_names = pick_delete_names(records, 50, seed=43)
start = time.perf_counter()
structure = build_structure(structure_name, records, buckets_count=buckets_count)
insert_time = time.perf_counter() - start
start = time.perf_counter()
do_find(structure_name, structure, existing_names, missing_names)
find_time = time.perf_counter() - start
start = time.perf_counter()
structure = do_delete(structure_name, structure, delete_names)
delete_time = time.perf_counter() - start
return {"insert": insert_time, "find": find_time, "delete": delete_time}
def run_experiments(n: int = 10000, buckets_count: int = 2048, repeats: int = 5):
records = generate_records(n, repeat_names=False)
records_shuffled, records_sorted = prepare_records_variants(records)
datasets = [
("случайный", records_shuffled),
("отсортированный", records_sorted),
]
structures = [
("LinkedList", "linked_list"),
("HashTable", "hash_table"),
("BST", "bst"),
]
operations = ("insert", "find", "delete")
rows = [["Структура", "Режим", "Операция", "Замер", "Время (сек)"]]
for mode_name, dataset_records in datasets:
for human_name, structure_name in structures:
times_by_op = {op: [] for op in operations}
for attempt in range(1, repeats + 1):
result = measure_once(structure_name, dataset_records, buckets_count=buckets_count)
for op_name in operations:
elapsed = result[op_name]
times_by_op[op_name].append(elapsed)
rows.append([human_name, mode_name, op_name, attempt, f"{elapsed:.10f}"])
for op_name in operations:
avg_time = sum(times_by_op[op_name]) / len(times_by_op[op_name])
rows.append([human_name, mode_name, op_name, "среднее", f"{avg_time:.10f}"])
return rows
def save_results_csv(rows, filename: str = "results.csv"):
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(rows)
def main():
rows = run_experiments(n=10000, buckets_count=2048, repeats=5)
save_results_csv(rows, "results.csv")
print("Saved results.csv")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,44 @@
from typing import Any, Dict, List, Optional
from linked_list import ll_insert, ll_find, ll_delete, ll_list_all
Bucket = Optional[Dict[str, Any]]
def _hash_name(name: str, buckets_count: int) -> int:
if buckets_count <= 0:
return 0
return sum(ord(ch) for ch in name) % buckets_count
def ht_insert(buckets: List[Bucket], name: str, phone: str) -> List[Bucket]:
if not buckets:
return buckets
index = _hash_name(name, len(buckets))
buckets[index] = ll_insert(buckets[index], name, phone)
return buckets
def ht_find(buckets: List[Bucket], name: str) -> Optional[str]:
if not buckets:
return None
index = _hash_name(name, len(buckets))
return ll_find(buckets[index], name)
def ht_delete(buckets: List[Bucket], name: str) -> List[Bucket]:
if not buckets:
return buckets
index = _hash_name(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
return buckets
def ht_list_all(buckets: List[Bucket]) -> List[Dict[str, str]]:
records: List[Dict[str, str]] = []
for head in buckets:
records.extend(ll_list_all(head))
return sorted(records, key=lambda x: x["name"])

View File

@ -0,0 +1,73 @@
from typing import Any, Dict, List, Optional
Node = Dict[str, Any]
def _make_node(name: str, phone: str) -> Node:
return {"name": name, "phone": phone, "next": None}
def sort_records(records: List[Dict[str, str]]) -> List[Dict[str, str]]:
return sorted(records, key=lambda x: x["name"])
def ll_insert(head: Optional[Node], name: str, phone: str) -> Node:
new_node = _make_node(name, phone)
if head is None:
return new_node
current = head
while current is not None:
if current["name"] == name:
current["phone"] = phone
return head
if current["next"] is None:
current["next"] = new_node
return head
current = current["next"]
return head
def ll_find(head: Optional[Node], name: str) -> Optional[str]:
current = head
while current is not None:
if current["name"] == name:
return current["phone"]
current = current["next"]
return None
def ll_delete(head: Optional[Node], name: str) -> Optional[Node]:
if head is None:
return None
if head["name"] == name:
return head["next"]
prev = head
current = head["next"]
while current is not None:
if current["name"] == name:
prev["next"] = current["next"]
return head
prev = current
current = current["next"]
return head
def ll_list_all(head: Optional[Node]) -> List[Dict[str, str]]:
records: List[Dict[str, str]] = []
current = head
while current is not None:
records.append({"name": current["name"], "phone": current["phone"]})
current = current["next"]
return sort_records(records)

View File

@ -0,0 +1,21 @@
from __future__ import annotations
import csv
from pathlib import Path
from experiments import run_experiments, save_results_csv
from plot_results import build_graphs, load_average_results
def main():
rows = run_experiments(n=10000, buckets_count=2048, repeats=5)
save_results_csv(rows, "results.csv")
averaged = load_average_results("results.csv")
build_graphs(averaged, output_dir="docs/data")
print("Done.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,66 @@
from __future__ import annotations
import csv
from collections import defaultdict
from pathlib import Path
import matplotlib.pyplot as plt
from typing import List, Dict, Any
# Карта соответствия заголовков CSV для удобства поддержки
_CSV_KEYS = {
"STRUCTURE": "Структура",
"MODE": "Режим",
"OP": "Операция",
"MEASURE": "Замер",
"TIME": "Время (сек)"
}
def load_average_results(csv_file: str) -> List[Dict[str, Any]]:
"""Загружает только строки со средними значениями из CSV."""
with open(csv_file, "r", encoding="utf-8") as fh:
return [
{
"structure": row[_CSV_KEYS["STRUCTURE"]],
"mode": row[_CSV_KEYS["MODE"]],
"operation": row[_CSV_KEYS["OP"]],
"time": float(row[_CSV_KEYS["TIME"]])
}
for row in csv.DictReader(fh)
if row[_CSV_KEYS["MEASURE"]] == "среднее"
]
def _render_chart(operation: str, data: List[Dict[str, Any]], save_path: Path) -> None:
"""Вспомогательная функция для отрисовки одного столбчатого графика."""
fig, ax = plt.subplots(figsize=(10, 5))
labels = [f"{rec['structure']}\n{rec['mode']}" for rec in data]
values = [rec["time"] for rec in data]
ax.bar(labels, values, color="cornflowerblue", edgecolor="navy")
ax.set_title(f"{operation.capitalize()} Performance Comparison")
ax.set_xlabel("Data Structure & Input Order")
ax.set_ylabel("Execution Time (seconds)")
ax.tick_params(axis="x", rotation=15)
fig.tight_layout()
fig.savefig(save_path, dpi=150)
plt.close(fig)
print(f"Saved chart: {save_path}")
def build_graphs(results: List[Dict[str, Any]], output_dir: str = "docs/data") -> None:
"""Группирует результаты по операциям и сохраняет графики."""
out_path = Path(output_dir)
out_path.mkdir(parents=True, exist_ok=True)
grouped = defaultdict(list)
for record in results:
grouped[record["operation"]].append(record)
for op_name in ("insert", "find", "delete"):
if op_name in grouped:
target_file = out_path / f"{op_name}.png"
_render_chart(op_name, grouped[op_name], target_file)
def main():
avg_data = load_average_results("results.csv")
build_graphs(avg_data)
if __name__ == "__main__":
main()

View File

@ -0,0 +1 @@
matplotlib>=3.8

View File

@ -0,0 +1,109 @@
Структура,Режим,Операция,Замер,Время (сек)
LinkedList,случайный,insert,1,6.8132659000
LinkedList,случайный,find,1,0.0553455000
LinkedList,случайный,delete,1,0.0304272000
LinkedList,случайный,insert,2,6.8263299000
LinkedList,случайный,find,2,0.0562645000
LinkedList,случайный,delete,2,0.0303858000
LinkedList,случайный,insert,3,6.8117682000
LinkedList,случайный,find,3,0.0567219000
LinkedList,случайный,delete,3,0.0302671000
LinkedList,случайный,insert,4,7.0393228000
LinkedList,случайный,find,4,0.0544359000
LinkedList,случайный,delete,4,0.0313359000
LinkedList,случайный,insert,5,7.0633509000
LinkedList,случайный,find,5,0.0552176000
LinkedList,случайный,delete,5,0.0318664000
LinkedList,случайный,insert,среднее,6.9108075400
LinkedList,случайный,find,среднее,0.0555970800
LinkedList,случайный,delete,среднее,0.0308564800
HashTable,случайный,insert,1,0.3898307000
HashTable,случайный,find,1,0.0022226000
HashTable,случайный,delete,1,0.0012723000
HashTable,случайный,insert,2,0.3844561000
HashTable,случайный,find,2,0.0024825000
HashTable,случайный,delete,2,0.0013425000
HashTable,случайный,insert,3,0.3740853000
HashTable,случайный,find,3,0.0026141000
HashTable,случайный,delete,3,0.0013710000
HashTable,случайный,insert,4,0.3870099000
HashTable,случайный,find,4,0.0022063000
HashTable,случайный,delete,4,0.0015940000
HashTable,случайный,insert,5,0.3869087000
HashTable,случайный,find,5,0.0022110000
HashTable,случайный,delete,5,0.0012524000
HashTable,случайный,insert,среднее,0.3844581400
HashTable,случайный,find,среднее,0.0023473000
HashTable,случайный,delete,среднее,0.0013664400
BST,случайный,insert,1,0.0375842000
BST,случайный,find,1,0.0003637000
BST,случайный,delete,1,0.0001893000
BST,случайный,insert,2,0.0364385000
BST,случайный,find,2,0.0003284000
BST,случайный,delete,2,0.0002408000
BST,случайный,insert,3,0.0359604000
BST,случайный,find,3,0.0003705000
BST,случайный,delete,3,0.0001883000
BST,случайный,insert,4,0.0371920000
BST,случайный,find,4,0.0003347000
BST,случайный,delete,4,0.0001711000
BST,случайный,insert,5,0.0385580000
BST,случайный,find,5,0.0003628000
BST,случайный,delete,5,0.0001828000
BST,случайный,insert,среднее,0.0371466200
BST,случайный,find,среднее,0.0003520200
BST,случайный,delete,среднее,0.0001944600
LinkedList,отсортированный,insert,1,6.7166487000
LinkedList,отсортированный,find,1,0.0614362000
LinkedList,отсортированный,delete,1,0.0324487000
LinkedList,отсортированный,insert,2,6.8582294000
LinkedList,отсортированный,find,2,0.0588546000
LinkedList,отсортированный,delete,2,0.0332572000
LinkedList,отсортированный,insert,3,6.6991410000
LinkedList,отсортированный,find,3,0.0521304000
LinkedList,отсортированный,delete,3,0.0296734000
LinkedList,отсортированный,insert,4,6.7166336000
LinkedList,отсортированный,find,4,0.0521848000
LinkedList,отсортированный,delete,4,0.0286064000
LinkedList,отсортированный,insert,5,6.6510829000
LinkedList,отсортированный,find,5,0.0547075000
LinkedList,отсортированный,delete,5,0.0285625000
LinkedList,отсортированный,insert,среднее,6.7283471200
LinkedList,отсортированный,find,среднее,0.0558627000
LinkedList,отсортированный,delete,среднее,0.0305096400
HashTable,отсортированный,insert,1,0.3645209000
HashTable,отсортированный,find,1,0.0032770000
HashTable,отсортированный,delete,1,0.0018631000
HashTable,отсортированный,insert,2,0.3579217000
HashTable,отсортированный,find,2,0.0032143000
HashTable,отсортированный,delete,2,0.0016427000
HashTable,отсортированный,insert,3,0.3684879000
HashTable,отсортированный,find,3,0.0026412000
HashTable,отсортированный,delete,3,0.0014870000
HashTable,отсортированный,insert,4,0.3873619000
HashTable,отсортированный,find,4,0.0028024000
HashTable,отсортированный,delete,4,0.0015786000
HashTable,отсортированный,insert,5,0.3642992000
HashTable,отсортированный,find,5,0.0031264000
HashTable,отсортированный,delete,5,0.0015886000
HashTable,отсортированный,insert,среднее,0.3685183200
HashTable,отсортированный,find,среднее,0.0030122600
HashTable,отсортированный,delete,среднее,0.0016320000
BST,отсортированный,insert,1,10.5552378000
BST,отсортированный,find,1,0.1016856000
BST,отсортированный,delete,1,0.0422728000
BST,отсортированный,insert,2,10.3035871000
BST,отсортированный,find,2,0.1008642000
BST,отсортированный,delete,2,0.0450330000
BST,отсортированный,insert,3,10.6304005000
BST,отсортированный,find,3,0.1073470000
BST,отсортированный,delete,3,0.0816121000
BST,отсортированный,insert,4,10.3183078000
BST,отсортированный,find,4,0.1005074000
BST,отсортированный,delete,4,0.0422195000
BST,отсортированный,insert,5,10.3131368000
BST,отсортированный,find,5,0.1001096000
BST,отсортированный,delete,5,0.0416660000
BST,отсортированный,insert,среднее,10.4241340000
BST,отсортированный,find,среднее,0.1021027600
BST,отсортированный,delete,среднее,0.0505606800
1 Структура Режим Операция Замер Время (сек)
2 LinkedList случайный insert 1 6.8132659000
3 LinkedList случайный find 1 0.0553455000
4 LinkedList случайный delete 1 0.0304272000
5 LinkedList случайный insert 2 6.8263299000
6 LinkedList случайный find 2 0.0562645000
7 LinkedList случайный delete 2 0.0303858000
8 LinkedList случайный insert 3 6.8117682000
9 LinkedList случайный find 3 0.0567219000
10 LinkedList случайный delete 3 0.0302671000
11 LinkedList случайный insert 4 7.0393228000
12 LinkedList случайный find 4 0.0544359000
13 LinkedList случайный delete 4 0.0313359000
14 LinkedList случайный insert 5 7.0633509000
15 LinkedList случайный find 5 0.0552176000
16 LinkedList случайный delete 5 0.0318664000
17 LinkedList случайный insert среднее 6.9108075400
18 LinkedList случайный find среднее 0.0555970800
19 LinkedList случайный delete среднее 0.0308564800
20 HashTable случайный insert 1 0.3898307000
21 HashTable случайный find 1 0.0022226000
22 HashTable случайный delete 1 0.0012723000
23 HashTable случайный insert 2 0.3844561000
24 HashTable случайный find 2 0.0024825000
25 HashTable случайный delete 2 0.0013425000
26 HashTable случайный insert 3 0.3740853000
27 HashTable случайный find 3 0.0026141000
28 HashTable случайный delete 3 0.0013710000
29 HashTable случайный insert 4 0.3870099000
30 HashTable случайный find 4 0.0022063000
31 HashTable случайный delete 4 0.0015940000
32 HashTable случайный insert 5 0.3869087000
33 HashTable случайный find 5 0.0022110000
34 HashTable случайный delete 5 0.0012524000
35 HashTable случайный insert среднее 0.3844581400
36 HashTable случайный find среднее 0.0023473000
37 HashTable случайный delete среднее 0.0013664400
38 BST случайный insert 1 0.0375842000
39 BST случайный find 1 0.0003637000
40 BST случайный delete 1 0.0001893000
41 BST случайный insert 2 0.0364385000
42 BST случайный find 2 0.0003284000
43 BST случайный delete 2 0.0002408000
44 BST случайный insert 3 0.0359604000
45 BST случайный find 3 0.0003705000
46 BST случайный delete 3 0.0001883000
47 BST случайный insert 4 0.0371920000
48 BST случайный find 4 0.0003347000
49 BST случайный delete 4 0.0001711000
50 BST случайный insert 5 0.0385580000
51 BST случайный find 5 0.0003628000
52 BST случайный delete 5 0.0001828000
53 BST случайный insert среднее 0.0371466200
54 BST случайный find среднее 0.0003520200
55 BST случайный delete среднее 0.0001944600
56 LinkedList отсортированный insert 1 6.7166487000
57 LinkedList отсортированный find 1 0.0614362000
58 LinkedList отсортированный delete 1 0.0324487000
59 LinkedList отсортированный insert 2 6.8582294000
60 LinkedList отсортированный find 2 0.0588546000
61 LinkedList отсортированный delete 2 0.0332572000
62 LinkedList отсортированный insert 3 6.6991410000
63 LinkedList отсортированный find 3 0.0521304000
64 LinkedList отсортированный delete 3 0.0296734000
65 LinkedList отсортированный insert 4 6.7166336000
66 LinkedList отсортированный find 4 0.0521848000
67 LinkedList отсортированный delete 4 0.0286064000
68 LinkedList отсортированный insert 5 6.6510829000
69 LinkedList отсортированный find 5 0.0547075000
70 LinkedList отсортированный delete 5 0.0285625000
71 LinkedList отсортированный insert среднее 6.7283471200
72 LinkedList отсортированный find среднее 0.0558627000
73 LinkedList отсортированный delete среднее 0.0305096400
74 HashTable отсортированный insert 1 0.3645209000
75 HashTable отсортированный find 1 0.0032770000
76 HashTable отсортированный delete 1 0.0018631000
77 HashTable отсортированный insert 2 0.3579217000
78 HashTable отсортированный find 2 0.0032143000
79 HashTable отсортированный delete 2 0.0016427000
80 HashTable отсортированный insert 3 0.3684879000
81 HashTable отсортированный find 3 0.0026412000
82 HashTable отсортированный delete 3 0.0014870000
83 HashTable отсортированный insert 4 0.3873619000
84 HashTable отсортированный find 4 0.0028024000
85 HashTable отсортированный delete 4 0.0015786000
86 HashTable отсортированный insert 5 0.3642992000
87 HashTable отсортированный find 5 0.0031264000
88 HashTable отсортированный delete 5 0.0015886000
89 HashTable отсортированный insert среднее 0.3685183200
90 HashTable отсортированный find среднее 0.0030122600
91 HashTable отсортированный delete среднее 0.0016320000
92 BST отсортированный insert 1 10.5552378000
93 BST отсортированный find 1 0.1016856000
94 BST отсортированный delete 1 0.0422728000
95 BST отсортированный insert 2 10.3035871000
96 BST отсортированный find 2 0.1008642000
97 BST отсортированный delete 2 0.0450330000
98 BST отсортированный insert 3 10.6304005000
99 BST отсортированный find 3 0.1073470000
100 BST отсортированный delete 3 0.0816121000
101 BST отсортированный insert 4 10.3183078000
102 BST отсортированный find 4 0.1005074000
103 BST отсортированный delete 4 0.0422195000
104 BST отсортированный insert 5 10.3131368000
105 BST отсортированный find 5 0.1001096000
106 BST отсортированный delete 5 0.0416660000
107 BST отсортированный insert среднее 10.4241340000
108 BST отсортированный find среднее 0.1021027600
109 BST отсортированный delete среднее 0.0505606800

View File

@ -0,0 +1,29 @@
import random
from typing import List, Tuple
Record = Tuple[str, str]
NAME_POOL = (
"User_Alex", "User_Bob", "User_Cat", "User_Dan", "User_Eva",
"User_Fox", "User_Geo", "User_Hen", "User_Ira", "User_Leo"
)
def generate_records(n: int, repeat_names: bool = False, seed: int = 42) -> List[Record]:
"""Генерирует n кортежей (имя, телефон)."""
rng = random.Random(seed)
if repeat_names:
return [
(rng.choice(NAME_POOL), str(rng.randint(10**9, 10**10 - 1)))
for _ in range(n)
]
return [
(f"User_{i:05d}", str(10**9 + i))
for i in range(n)
]
def prepare_records_variants(records: List[Record], seed: int = 42) -> Tuple[List[Record], List[Record]]:
"""Возвращает пару: (перемешанный список, отсортированный по имени список)."""
shuffled = records.copy()
random.Random(seed).shuffle(shuffled)
sorted_records = sorted(records, key=lambda rec: rec[0])
return shuffled, sorted_records

View File

View File

@ -0,0 +1,7 @@
from abc import ABC, abstractmethod
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
raise NotImplementedError

View File

@ -0,0 +1,52 @@
from core.cell import Cell
from core.maze import Maze
from builders.maze_builder import MazeBuilder
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, "r", encoding="utf-8") as f:
lines = [line.rstrip("\n") for line in f]
if not lines:
raise ValueError("Maze file is empty")
width = max(len(line) for line in lines)
height = len(lines)
cells = []
startCell = None
exitCell = None
for y, line in enumerate(lines):
row = []
for x in range(width):
ch = line[x] if x < len(line) else "#"
if ch == "#":
cell = Cell(x, y, isWall=True)
elif ch == "S":
if startCell is not None:
raise ValueError("Multiple start cells found")
cell = Cell(x, y, isWall=False, isStart=True)
startCell = cell
elif ch == "E":
if exitCell is not None:
raise ValueError("Multiple exit cells found")
cell = Cell(x, y, isWall=False, isExit=True)
exitCell = cell
elif ch in (" ", "."):
cell = Cell(x, y, isWall=False)
elif ch.isdigit():
cell = Cell(x, y, isWall=False, weight=max(1, int(ch)))
else:
raise ValueError(f"Unsupported symbol '{ch}' at ({x}, {y})")
row.append(cell)
cells.append(row)
if startCell is None:
raise ValueError("Start cell 'S' not found")
if exitCell is None:
raise ValueError("Exit cell 'E' not found")
return Maze(cells, width, height, startCell, exitCell)

View File

View File

@ -0,0 +1,11 @@
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
raise NotImplementedError
@abstractmethod
def undo(self):
raise NotImplementedError

View File

@ -0,0 +1,37 @@
from commands.command import Command
class MoveCommand(Command):
DIRECTION_TO_DELTA = {
"W": (0, -1),
"A": (-1, 0),
"S": (0, 1),
"D": (1, 0),
}
def __init__(self, player, maze, direction):
self.player = player
self.maze = maze
self.direction = direction.upper()
self.previousCell = None
def execute(self):
if self.direction not in self.DIRECTION_TO_DELTA:
return False
dx, dy = self.DIRECTION_TO_DELTA[self.direction]
current = self.player.currentCell
new_cell = self.maze.getCell(current.x + dx, current.y + dy)
if new_cell is None or not new_cell.isPassable():
return False
self.previousCell = current
self.player.setCell(new_cell)
return True
def undo(self):
if self.previousCell is None:
return False
self.player.setCell(self.previousCell)
return True

View File

@ -0,0 +1,30 @@
from commands.move_command import MoveCommand
class GameController:
def __init__(self, maze, player, view):
self.maze = maze
self.player = player
self.view = view
self.history = []
def move(self, direction):
command = MoveCommand(self.player, self.maze, direction)
if command.execute():
self.history.append(command)
self.view.update({"type": "move", "direction": direction})
self.view.render(self.maze, player_position=self.player.currentCell)
return True
print("Cannot move there")
return False
def undo(self):
if not self.history:
print("Nothing to undo")
return False
command = self.history.pop()
if command.undo():
self.view.update({"type": "undo"})
self.view.render(self.maze, player_position=self.player.currentCell)
return True
return False

View File

View File

@ -0,0 +1,26 @@
from dataclasses import dataclass
@dataclass
class Cell:
x: int
y: int
isWall: bool = False
isStart: bool = False
isExit: bool = False
weight: int = 1
def isPassable(self):
return not self.isWall
def __repr__(self):
parts = [f"Cell({self.x}, {self.y}"]
if self.isWall:
parts.append("WALL")
if self.isStart:
parts.append("START")
if self.isExit:
parts.append("EXIT")
if self.weight != 1:
parts.append(f"w={self.weight}")
return ", ".join(parts) + ")"

View File

@ -0,0 +1,49 @@
class Maze:
def __init__(self, cells, width, height, startCell=None, exitCell=None):
self.cells = cells
self.width = width
self.height = height
self.startCell = startCell
self.exitCell = exitCell
def getCell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def getNeighbors(self, cell):
neighbors = []
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.getCell(nx, ny)
if neighbor is not None and neighbor.isPassable():
neighbors.append(neighbor)
return neighbors
def render_lines(self, player_position=None, path=None):
path_set = {(c.x, c.y) for c in path} if path else set()
player_pos = None if player_position is None else (player_position.x, player_position.y)
lines = []
for y in range(self.height):
row = []
for x in range(self.width):
cell = self.cells[y][x]
if player_pos == (x, y):
row.append("P")
elif cell.isStart:
row.append("S")
elif cell.isExit:
row.append("E")
elif cell.isWall:
row.append("#")
elif (x, y) in path_set:
row.append("*")
elif cell.weight > 1:
row.append(str(cell.weight))
else:
row.append(" ")
lines.append("".join(row))
return lines
def render(self, player_position=None, path=None):
return "\n".join(self.render_lines(player_position=player_position, path=path))

View File

@ -0,0 +1,6 @@
class Player:
def __init__(self, currentCell):
self.currentCell = currentCell
def setCell(self, cell):
self.currentCell = cell

View File

@ -0,0 +1,11 @@
from dataclasses import dataclass, field
@dataclass
class SearchStats:
timeMs: float
visitedCells: int
pathLength: int
path: list = field(default_factory=list)
found: bool = False
algorithm: str = ""

Binary file not shown.

View File

@ -0,0 +1,225 @@
from pathlib import Path
from statistics import mean
import csv
import random
import matplotlib.pyplot as plt
from core.cell import Cell
from core.maze import Maze
from solver.maze_solver import MazeSolver
from strategies.astar_strategy import AStarStrategy
from strategies.bfs_strategy import BFSStrategy
from strategies.dfs_strategy import DFSStrategy
from strategies.dijkstra_strategy import DijkstraStrategy
BASE_DIR = Path(__file__).resolve().parent
OUT_DIR = BASE_DIR / "experiment_results"
def build_maze_from_symbols(lines):
height = len(lines)
width = max(len(line) for line in lines)
cells = []
start = None
exit_cell = None
for y, line in enumerate(lines):
row = []
for x in range(width):
ch = line[x] if x < len(line) else "#"
if ch == "#":
cell = Cell(x, y, isWall=True)
elif ch == "S":
cell = Cell(x, y, isWall=False, isStart=True)
start = cell
elif ch == "E":
cell = Cell(x, y, isWall=False, isExit=True)
exit_cell = cell
elif ch == " " or ch == ".":
cell = Cell(x, y, isWall=False)
elif ch.isdigit():
cell = Cell(x, y, isWall=False, weight=int(ch))
else:
raise ValueError(f"Unknown symbol '{ch}' at {x},{y}")
row.append(cell)
cells.append(row)
return Maze(cells, width, height, start, exit_cell)
def generate_empty_maze(width, height):
lines = [" " * width for _ in range(height)]
lines = [list(row) for row in lines]
lines[1][1] = "S"
lines[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in lines])
def generate_simple_maze(width, height):
grid = [["#" for _ in range(width)] for _ in range(height)]
for x in range(1, width - 1):
grid[1][x] = " "
for y in range(1, height - 1):
grid[y][width - 2] = " "
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def generate_branching_maze(width, height, seed=42, wall_density=0.30):
rng = random.Random(seed)
grid = [["#" for _ in range(width)] for _ in range(height)]
x, y = 1, 1
grid[y][x] = "S"
while (x, y) != (width - 2, height - 2):
candidates = []
for dx, dy in [(1, 0), (0, 1)]:
nx, ny = x + dx, y + dy
if 1 <= nx < width - 1 and 1 <= ny < height - 1:
candidates.append((nx, ny))
if not candidates:
break
x, y = rng.choice(candidates)
grid[y][x] = " "
grid[height - 2][width - 2] = "E"
# carve extra corridors and dead ends
for yy in range(1, height - 1):
for xx in range(1, width - 1):
if grid[yy][xx] == "#" and rng.random() > wall_density:
grid[yy][xx] = " "
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def generate_no_path_maze(width, height):
grid = [[" " for _ in range(width)] for _ in range(height)]
for x in range(width):
grid[height // 2][x] = "#"
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def generate_weighted_maze(width, height, seed=123):
rng = random.Random(seed)
grid = [[" " for _ in range(width)] for _ in range(height)]
for y in range(height):
for x in range(width):
r = rng.random()
if r < 0.12:
grid[y][x] = "#"
elif r < 0.25:
grid[y][x] = "3"
elif r < 0.40:
grid[y][x] = "2"
else:
grid[y][x] = "1"
# ensure path-ish
for x in range(width):
grid[1][x] = "1"
for y in range(1, height):
grid[y][width - 2] = "1"
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def bench_one_maze(maze_name, maze, strategies, repeats=5):
summary_rows = []
raw_rows = []
for strategy_name, strategy_factory in strategies:
times, visiteds, lengths = [], [], []
for run in range(1, repeats + 1):
solver = MazeSolver(maze)
solver.setStrategy(strategy_factory())
stats = solver.solve()
raw_rows.append([maze_name, strategy_name, run, f"{stats.timeMs:.6f}", stats.visitedCells, stats.pathLength])
times.append(stats.timeMs)
visiteds.append(stats.visitedCells)
lengths.append(stats.pathLength)
summary_rows.append([maze_name, strategy_name, f"{mean(times):.6f}", f"{mean(visiteds):.2f}", f"{mean(lengths):.2f}", repeats])
return summary_rows, raw_rows
def save_csv(path, rows):
with open(path, "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
def plot_summary(summary_rows):
by_maze = {}
for row in summary_rows[1:]:
maze_name, strategy, avg_time, avg_visited, avg_len, runs = row
by_maze.setdefault(maze_name, []).append((strategy, float(avg_time), float(avg_visited), float(avg_len)))
for maze_name, items in by_maze.items():
items.sort(key=lambda t: t[0])
strategies = [i[0] for i in items]
x = list(range(len(strategies)))
plt.figure(figsize=(8, 4))
plt.bar(x, [i[1] for i in items])
plt.xticks(x, strategies)
plt.ylabel("ms")
plt.title(f"{maze_name} — avg time")
plt.tight_layout()
plt.savefig(OUT_DIR / f"{maze_name}_time.png", dpi=150)
plt.close()
plt.figure(figsize=(8, 4))
plt.bar(x, [i[2] for i in items])
plt.xticks(x, strategies)
plt.ylabel("cells")
plt.title(f"{maze_name} — visited cells")
plt.tight_layout()
plt.savefig(OUT_DIR / f"{maze_name}_visited.png", dpi=150)
plt.close()
plt.figure(figsize=(8, 4))
plt.bar(x, [i[3] for i in items])
plt.xticks(x, strategies)
plt.ylabel("cells")
plt.title(f"{maze_name} — path length")
plt.tight_layout()
plt.savefig(OUT_DIR / f"{maze_name}_length.png", dpi=150)
plt.close()
def main():
OUT_DIR.mkdir(exist_ok=True)
strategies = [
("BFS", BFSStrategy),
("DFS", DFSStrategy),
("A*", AStarStrategy),
("Dijkstra", DijkstraStrategy),
]
mazes = [
("small_10x10", generate_simple_maze(10, 10)),
("medium_50x50", generate_branching_maze(50, 50)),
("large_100x100", generate_branching_maze(100, 100, seed=99, wall_density=0.35)),
("empty_30x30", generate_empty_maze(30, 30)),
("no_path_30x30", generate_no_path_maze(30, 30)),
("weighted_30x30", generate_weighted_maze(30, 30)),
]
summary = [["maze", "strategy", "avg_time_ms", "avg_visited_cells", "avg_path_length", "runs"]]
raw = [["maze", "strategy", "run", "time_ms", "visited_cells", "path_length"]]
for maze_name, maze in mazes:
s_rows, r_rows = bench_one_maze(maze_name, maze, strategies, repeats=5)
summary.extend(s_rows)
raw.extend(r_rows)
save_csv(OUT_DIR / "summary.csv", summary)
save_csv(OUT_DIR / "raw.csv", raw)
plot_summary(summary)
print("Saved to", OUT_DIR.resolve())
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,121 @@
maze,strategy,run,time_ms,visited_cells,path_length
small_10x10,BFS,1,0.086300,15,15
small_10x10,BFS,2,0.061100,15,15
small_10x10,BFS,3,0.059300,15,15
small_10x10,BFS,4,0.058400,15,15
small_10x10,BFS,5,0.058500,15,15
small_10x10,DFS,1,0.073400,15,15
small_10x10,DFS,2,0.063500,15,15
small_10x10,DFS,3,0.062500,15,15
small_10x10,DFS,4,0.062700,15,15
small_10x10,DFS,5,0.070900,15,15
small_10x10,A*,1,0.110100,15,15
small_10x10,A*,2,0.089200,15,15
small_10x10,A*,3,0.087800,15,15
small_10x10,A*,4,0.087600,15,15
small_10x10,A*,5,0.087000,15,15
small_10x10,Dijkstra,1,0.290000,15,15
small_10x10,Dijkstra,2,0.083300,15,15
small_10x10,Dijkstra,3,0.091500,15,15
small_10x10,Dijkstra,4,0.081000,15,15
small_10x10,Dijkstra,5,0.080400,15,15
medium_50x50,BFS,1,6.799200,1579,95
medium_50x50,BFS,2,6.960100,1579,95
medium_50x50,BFS,3,6.337000,1579,95
medium_50x50,BFS,4,7.431700,1579,95
medium_50x50,BFS,5,6.517900,1579,95
medium_50x50,DFS,1,6.463000,1277,647
medium_50x50,DFS,2,6.815500,1277,647
medium_50x50,DFS,3,5.816100,1277,647
medium_50x50,DFS,4,6.492400,1277,647
medium_50x50,DFS,5,6.532500,1277,647
medium_50x50,A*,1,6.940500,927,95
medium_50x50,A*,2,7.275400,927,95
medium_50x50,A*,3,7.062500,927,95
medium_50x50,A*,4,7.727600,927,95
medium_50x50,A*,5,7.321000,927,95
medium_50x50,Dijkstra,1,11.483200,1579,95
medium_50x50,Dijkstra,2,11.194200,1579,95
medium_50x50,Dijkstra,3,11.255200,1579,95
medium_50x50,Dijkstra,4,10.512500,1579,95
medium_50x50,Dijkstra,5,10.696400,1579,95
large_100x100,BFS,1,25.623500,5566,195
large_100x100,BFS,2,24.348800,5566,195
large_100x100,BFS,3,25.452600,5566,195
large_100x100,BFS,4,30.516900,5566,195
large_100x100,BFS,5,33.694700,5566,195
large_100x100,DFS,1,19.415200,3543,1531
large_100x100,DFS,2,19.919000,3543,1531
large_100x100,DFS,3,19.104600,3543,1531
large_100x100,DFS,4,20.000600,3543,1531
large_100x100,DFS,5,17.840200,3543,1531
large_100x100,A*,1,7.509300,853,195
large_100x100,A*,2,7.221200,853,195
large_100x100,A*,3,6.486700,853,195
large_100x100,A*,4,6.357600,853,195
large_100x100,A*,5,6.723800,853,195
large_100x100,Dijkstra,1,40.782300,5571,195
large_100x100,Dijkstra,2,41.155000,5571,195
large_100x100,Dijkstra,3,39.456200,5571,195
large_100x100,Dijkstra,4,41.388700,5571,195
large_100x100,Dijkstra,5,40.962500,5571,195
empty_30x30,BFS,1,4.143200,896,55
empty_30x30,BFS,2,3.987000,896,55
empty_30x30,BFS,3,3.777100,896,55
empty_30x30,BFS,4,3.682300,896,55
empty_30x30,BFS,5,3.737900,896,55
empty_30x30,DFS,1,4.024200,842,815
empty_30x30,DFS,2,4.333900,842,815
empty_30x30,DFS,3,5.411000,842,815
empty_30x30,DFS,4,4.677200,842,815
empty_30x30,DFS,5,5.177400,842,815
empty_30x30,A*,1,6.603700,784,55
empty_30x30,A*,2,6.200600,784,55
empty_30x30,A*,3,6.798400,784,55
empty_30x30,A*,4,7.178500,784,55
empty_30x30,A*,5,6.660800,784,55
empty_30x30,Dijkstra,1,6.396000,896,55
empty_30x30,Dijkstra,2,6.275200,896,55
empty_30x30,Dijkstra,3,6.845700,896,55
empty_30x30,Dijkstra,4,6.531200,896,55
empty_30x30,Dijkstra,5,6.783400,896,55
no_path_30x30,BFS,1,2.000100,450,0
no_path_30x30,BFS,2,1.797900,450,0
no_path_30x30,BFS,3,1.796200,450,0
no_path_30x30,BFS,4,1.774100,450,0
no_path_30x30,BFS,5,1.775200,450,0
no_path_30x30,DFS,1,2.090400,450,0
no_path_30x30,DFS,2,2.222600,450,0
no_path_30x30,DFS,3,2.454300,450,0
no_path_30x30,DFS,4,2.476200,450,0
no_path_30x30,DFS,5,2.073700,450,0
no_path_30x30,A*,1,3.651700,450,0
no_path_30x30,A*,2,3.495200,450,0
no_path_30x30,A*,3,3.754200,450,0
no_path_30x30,A*,4,3.286800,450,0
no_path_30x30,A*,5,3.335200,450,0
no_path_30x30,Dijkstra,1,3.050900,450,0
no_path_30x30,Dijkstra,2,3.109900,450,0
no_path_30x30,Dijkstra,3,3.292500,450,0
no_path_30x30,Dijkstra,4,3.418600,450,0
no_path_30x30,Dijkstra,5,3.212100,450,0
weighted_30x30,BFS,1,3.418900,788,55
weighted_30x30,BFS,2,3.368200,788,55
weighted_30x30,BFS,3,3.516400,788,55
weighted_30x30,BFS,4,3.224300,788,55
weighted_30x30,BFS,5,3.131100,788,55
weighted_30x30,DFS,1,3.291200,693,479
weighted_30x30,DFS,2,3.362300,693,479
weighted_30x30,DFS,3,3.523200,693,479
weighted_30x30,DFS,4,3.521400,693,479
weighted_30x30,DFS,5,3.332300,693,479
weighted_30x30,A*,1,1.181000,126,55
weighted_30x30,A*,2,1.080200,126,55
weighted_30x30,A*,3,1.368400,126,55
weighted_30x30,A*,4,1.109800,126,55
weighted_30x30,A*,5,1.079300,126,55
weighted_30x30,Dijkstra,1,6.112700,781,55
weighted_30x30,Dijkstra,2,5.464800,781,55
weighted_30x30,Dijkstra,3,5.794500,781,55
weighted_30x30,Dijkstra,4,6.171700,781,55
weighted_30x30,Dijkstra,5,6.640500,781,55
1 maze strategy run time_ms visited_cells path_length
2 small_10x10 BFS 1 0.086300 15 15
3 small_10x10 BFS 2 0.061100 15 15
4 small_10x10 BFS 3 0.059300 15 15
5 small_10x10 BFS 4 0.058400 15 15
6 small_10x10 BFS 5 0.058500 15 15
7 small_10x10 DFS 1 0.073400 15 15
8 small_10x10 DFS 2 0.063500 15 15
9 small_10x10 DFS 3 0.062500 15 15
10 small_10x10 DFS 4 0.062700 15 15
11 small_10x10 DFS 5 0.070900 15 15
12 small_10x10 A* 1 0.110100 15 15
13 small_10x10 A* 2 0.089200 15 15
14 small_10x10 A* 3 0.087800 15 15
15 small_10x10 A* 4 0.087600 15 15
16 small_10x10 A* 5 0.087000 15 15
17 small_10x10 Dijkstra 1 0.290000 15 15
18 small_10x10 Dijkstra 2 0.083300 15 15
19 small_10x10 Dijkstra 3 0.091500 15 15
20 small_10x10 Dijkstra 4 0.081000 15 15
21 small_10x10 Dijkstra 5 0.080400 15 15
22 medium_50x50 BFS 1 6.799200 1579 95
23 medium_50x50 BFS 2 6.960100 1579 95
24 medium_50x50 BFS 3 6.337000 1579 95
25 medium_50x50 BFS 4 7.431700 1579 95
26 medium_50x50 BFS 5 6.517900 1579 95
27 medium_50x50 DFS 1 6.463000 1277 647
28 medium_50x50 DFS 2 6.815500 1277 647
29 medium_50x50 DFS 3 5.816100 1277 647
30 medium_50x50 DFS 4 6.492400 1277 647
31 medium_50x50 DFS 5 6.532500 1277 647
32 medium_50x50 A* 1 6.940500 927 95
33 medium_50x50 A* 2 7.275400 927 95
34 medium_50x50 A* 3 7.062500 927 95
35 medium_50x50 A* 4 7.727600 927 95
36 medium_50x50 A* 5 7.321000 927 95
37 medium_50x50 Dijkstra 1 11.483200 1579 95
38 medium_50x50 Dijkstra 2 11.194200 1579 95
39 medium_50x50 Dijkstra 3 11.255200 1579 95
40 medium_50x50 Dijkstra 4 10.512500 1579 95
41 medium_50x50 Dijkstra 5 10.696400 1579 95
42 large_100x100 BFS 1 25.623500 5566 195
43 large_100x100 BFS 2 24.348800 5566 195
44 large_100x100 BFS 3 25.452600 5566 195
45 large_100x100 BFS 4 30.516900 5566 195
46 large_100x100 BFS 5 33.694700 5566 195
47 large_100x100 DFS 1 19.415200 3543 1531
48 large_100x100 DFS 2 19.919000 3543 1531
49 large_100x100 DFS 3 19.104600 3543 1531
50 large_100x100 DFS 4 20.000600 3543 1531
51 large_100x100 DFS 5 17.840200 3543 1531
52 large_100x100 A* 1 7.509300 853 195
53 large_100x100 A* 2 7.221200 853 195
54 large_100x100 A* 3 6.486700 853 195
55 large_100x100 A* 4 6.357600 853 195
56 large_100x100 A* 5 6.723800 853 195
57 large_100x100 Dijkstra 1 40.782300 5571 195
58 large_100x100 Dijkstra 2 41.155000 5571 195
59 large_100x100 Dijkstra 3 39.456200 5571 195
60 large_100x100 Dijkstra 4 41.388700 5571 195
61 large_100x100 Dijkstra 5 40.962500 5571 195
62 empty_30x30 BFS 1 4.143200 896 55
63 empty_30x30 BFS 2 3.987000 896 55
64 empty_30x30 BFS 3 3.777100 896 55
65 empty_30x30 BFS 4 3.682300 896 55
66 empty_30x30 BFS 5 3.737900 896 55
67 empty_30x30 DFS 1 4.024200 842 815
68 empty_30x30 DFS 2 4.333900 842 815
69 empty_30x30 DFS 3 5.411000 842 815
70 empty_30x30 DFS 4 4.677200 842 815
71 empty_30x30 DFS 5 5.177400 842 815
72 empty_30x30 A* 1 6.603700 784 55
73 empty_30x30 A* 2 6.200600 784 55
74 empty_30x30 A* 3 6.798400 784 55
75 empty_30x30 A* 4 7.178500 784 55
76 empty_30x30 A* 5 6.660800 784 55
77 empty_30x30 Dijkstra 1 6.396000 896 55
78 empty_30x30 Dijkstra 2 6.275200 896 55
79 empty_30x30 Dijkstra 3 6.845700 896 55
80 empty_30x30 Dijkstra 4 6.531200 896 55
81 empty_30x30 Dijkstra 5 6.783400 896 55
82 no_path_30x30 BFS 1 2.000100 450 0
83 no_path_30x30 BFS 2 1.797900 450 0
84 no_path_30x30 BFS 3 1.796200 450 0
85 no_path_30x30 BFS 4 1.774100 450 0
86 no_path_30x30 BFS 5 1.775200 450 0
87 no_path_30x30 DFS 1 2.090400 450 0
88 no_path_30x30 DFS 2 2.222600 450 0
89 no_path_30x30 DFS 3 2.454300 450 0
90 no_path_30x30 DFS 4 2.476200 450 0
91 no_path_30x30 DFS 5 2.073700 450 0
92 no_path_30x30 A* 1 3.651700 450 0
93 no_path_30x30 A* 2 3.495200 450 0
94 no_path_30x30 A* 3 3.754200 450 0
95 no_path_30x30 A* 4 3.286800 450 0
96 no_path_30x30 A* 5 3.335200 450 0
97 no_path_30x30 Dijkstra 1 3.050900 450 0
98 no_path_30x30 Dijkstra 2 3.109900 450 0
99 no_path_30x30 Dijkstra 3 3.292500 450 0
100 no_path_30x30 Dijkstra 4 3.418600 450 0
101 no_path_30x30 Dijkstra 5 3.212100 450 0
102 weighted_30x30 BFS 1 3.418900 788 55
103 weighted_30x30 BFS 2 3.368200 788 55
104 weighted_30x30 BFS 3 3.516400 788 55
105 weighted_30x30 BFS 4 3.224300 788 55
106 weighted_30x30 BFS 5 3.131100 788 55
107 weighted_30x30 DFS 1 3.291200 693 479
108 weighted_30x30 DFS 2 3.362300 693 479
109 weighted_30x30 DFS 3 3.523200 693 479
110 weighted_30x30 DFS 4 3.521400 693 479
111 weighted_30x30 DFS 5 3.332300 693 479
112 weighted_30x30 A* 1 1.181000 126 55
113 weighted_30x30 A* 2 1.080200 126 55
114 weighted_30x30 A* 3 1.368400 126 55
115 weighted_30x30 A* 4 1.109800 126 55
116 weighted_30x30 A* 5 1.079300 126 55
117 weighted_30x30 Dijkstra 1 6.112700 781 55
118 weighted_30x30 Dijkstra 2 5.464800 781 55
119 weighted_30x30 Dijkstra 3 5.794500 781 55
120 weighted_30x30 Dijkstra 4 6.171700 781 55
121 weighted_30x30 Dijkstra 5 6.640500 781 55

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,25 @@
maze,strategy,avg_time_ms,avg_visited_cells,avg_path_length,runs
small_10x10,BFS,0.064720,15.00,15.00,5
small_10x10,DFS,0.066600,15.00,15.00,5
small_10x10,A*,0.092340,15.00,15.00,5
small_10x10,Dijkstra,0.125240,15.00,15.00,5
medium_50x50,BFS,6.809180,1579.00,95.00,5
medium_50x50,DFS,6.423900,1277.00,647.00,5
medium_50x50,A*,7.265400,927.00,95.00,5
medium_50x50,Dijkstra,11.028300,1579.00,95.00,5
large_100x100,BFS,27.927300,5566.00,195.00,5
large_100x100,DFS,19.255920,3543.00,1531.00,5
large_100x100,A*,6.859720,853.00,195.00,5
large_100x100,Dijkstra,40.748940,5571.00,195.00,5
empty_30x30,BFS,3.865500,896.00,55.00,5
empty_30x30,DFS,4.724740,842.00,815.00,5
empty_30x30,A*,6.688400,784.00,55.00,5
empty_30x30,Dijkstra,6.566300,896.00,55.00,5
no_path_30x30,BFS,1.828700,450.00,0.00,5
no_path_30x30,DFS,2.263440,450.00,0.00,5
no_path_30x30,A*,3.504620,450.00,0.00,5
no_path_30x30,Dijkstra,3.216800,450.00,0.00,5
weighted_30x30,BFS,3.331780,788.00,55.00,5
weighted_30x30,DFS,3.406080,693.00,479.00,5
weighted_30x30,A*,1.163740,126.00,55.00,5
weighted_30x30,Dijkstra,6.036840,781.00,55.00,5
1 maze strategy avg_time_ms avg_visited_cells avg_path_length runs
2 small_10x10 BFS 0.064720 15.00 15.00 5
3 small_10x10 DFS 0.066600 15.00 15.00 5
4 small_10x10 A* 0.092340 15.00 15.00 5
5 small_10x10 Dijkstra 0.125240 15.00 15.00 5
6 medium_50x50 BFS 6.809180 1579.00 95.00 5
7 medium_50x50 DFS 6.423900 1277.00 647.00 5
8 medium_50x50 A* 7.265400 927.00 95.00 5
9 medium_50x50 Dijkstra 11.028300 1579.00 95.00 5
10 large_100x100 BFS 27.927300 5566.00 195.00 5
11 large_100x100 DFS 19.255920 3543.00 1531.00 5
12 large_100x100 A* 6.859720 853.00 195.00 5
13 large_100x100 Dijkstra 40.748940 5571.00 195.00 5
14 empty_30x30 BFS 3.865500 896.00 55.00 5
15 empty_30x30 DFS 4.724740 842.00 815.00 5
16 empty_30x30 A* 6.688400 784.00 55.00 5
17 empty_30x30 Dijkstra 6.566300 896.00 55.00 5
18 no_path_30x30 BFS 1.828700 450.00 0.00 5
19 no_path_30x30 DFS 2.263440 450.00 0.00 5
20 no_path_30x30 A* 3.504620 450.00 0.00 5
21 no_path_30x30 Dijkstra 3.216800 450.00 0.00 5
22 weighted_30x30 BFS 3.331780 788.00 55.00 5
23 weighted_30x30 DFS 3.406080 693.00 479.00 5
24 weighted_30x30 A* 1.163740 126.00 55.00 5
25 weighted_30x30 Dijkstra 6.036840 781.00 55.00 5

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,59 @@
from builders.text_file_maze_builder import TextFileMazeBuilder
from core.player import Player
from observer.console_view import ConsoleView
from solver.maze_solver import MazeSolver
from strategies.astar_strategy import AStarStrategy
from strategies.bfs_strategy import BFSStrategy
from strategies.dfs_strategy import DFSStrategy
from controller.game_controller import GameController
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
def run_demo():
builder = TextFileMazeBuilder()
maze = builder.buildFromFile(str(BASE_DIR / "mazes" / "maze_small.txt"))
view = ConsoleView()
view.update({"type": "maze_loaded", "message": "Maze loaded"})
view.render(maze)
solver = MazeSolver(maze)
solver.addObserver(view)
for strategy in (BFSStrategy(), DFSStrategy(), AStarStrategy()):
solver.setStrategy(strategy)
stats = solver.solve()
print()
print(f"=== {strategy.name} ===")
print(f"Time: {stats.timeMs:.3f} ms")
print(f"Visited cells: {stats.visitedCells}")
print(f"Path length: {stats.pathLength}")
print(f"Path found: {'yes' if stats.found else 'no'}")
view.render(maze, path=stats.path)
player = Player(maze.startCell)
controller = GameController(maze, player, view)
print("Manual mode: W/A/S/D move, Z undo, Q quit")
view.render(maze, player_position=player.currentCell)
while True:
cmd = input("Command: ").strip().upper()
if cmd == "Q":
break
if cmd == "Z":
controller.undo()
elif cmd in {"W", "A", "S", "D"}:
controller.move(cmd)
else:
print("Unknown command")
if __name__ == "__main__":
run_demo()

View File

@ -0,0 +1,9 @@
S
E

View File

@ -0,0 +1,11 @@
####################################################################################################
#S # # # # # # # # # # # # # # # E#
# # ### ### # ###### # ### # ## # #### # ####### # #### # # ### ## # ## # # ## # ## # ##### ### ##
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### # ######## # ### # ## # #### # ####### ## ### # # #### ####### ## ####### ####### # ### ##
# # # # # # # # # # # # # # # # # # # # #
### # # ###### # ########### ########### ### ####### # ####### ### # # ###### # ### ### # ### ####
# # # # # # # # # # # # # # # # # # # # # #
# ### ###### # ##### # ### # ####### # ### ### ## # ###### # ### # ### ###### # ### # ### ### ## #
# # # # # # # # #
####################################################################################################

View File

@ -0,0 +1,11 @@
##################################################
#S # # # # # # E#
# # ### ### # ###### # ### # ## # #### # ####### ##
# # # # # # # # # # # # # #
# ##### # ######## # ### # ## # #### # ####### ## #
# # # # # # # # # #
### # # ###### # ########### ########### ### ######
# # # # # # # # # # #
# ### ###### # ##### # ### # ####### # ### ### ## #
# # # # #
##################################################

View File

@ -0,0 +1,9 @@
##########
#S #
# ###### #
# # #
##########
# #E#
# ###### #
# #
##########

View File

@ -0,0 +1,7 @@
##########
#S #E#
# ## # # ##
# # #
# #### # #
# # #
##########

View File

@ -0,0 +1,10 @@
1111111111111111111111111111
1S11111111111111111111111111
1111111111111111111111111111
1111111111111111111111111111
1111111111111222222222222111
1111111111111222222222222111
1111111111111333333333333111
1111111111111333333333333111
111111111111111111111111111E
1111111111111111111111111111

View File

View File

@ -0,0 +1,26 @@
import os
from observer.observer import Observer
class ConsoleView(Observer):
def update(self, event):
if isinstance(event, str):
print(f"[EVENT] {event}")
elif isinstance(event, dict):
event_type = event.get("type", "unknown")
if event_type == "search_finished":
stats = event.get("stats")
print(f"[EVENT] search finished: {stats}")
else:
print(f"[EVENT] {event_type}: {event}")
else:
print("[EVENT] unknown")
def clear(self):
os.system("cls" if os.name == "nt" else "clear")
def render(self, maze, player_position=None, path=None, clear_screen=False):
if clear_screen:
self.clear()
print(maze.render(player_position=player_position, path=path))
print()

View File

@ -0,0 +1,7 @@
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, event):
raise NotImplementedError

View File

@ -0,0 +1 @@
matplotlib

View File

View File

@ -0,0 +1,50 @@
import time
from core.search_stats import SearchStats
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def setStrategy(self, strategy):
self.strategy = strategy
def addObserver(self, observer):
if observer not in self.observers:
self.observers.append(observer)
def removeObserver(self, observer):
if observer in self.observers:
self.observers.remove(observer)
def notify(self, event):
for observer in self.observers:
observer.update(event)
def solve(self):
if self.strategy is None:
raise ValueError("Strategy is not set")
self.notify({"type": "search_started", "strategy": self.strategy.name})
start_time = time.perf_counter()
path = self.strategy.findPath(self.maze, self.maze.startCell, self.maze.exitCell)
end_time = time.perf_counter()
stats = SearchStats(
timeMs=(end_time - start_time) * 1000.0,
visitedCells=getattr(self.strategy, "visitedCount", 0),
pathLength=len(path),
path=path,
found=bool(path),
algorithm=getattr(self.strategy, "name", "")
)
if stats.found:
self.notify({"type": "path_found", "strategy": stats.algorithm, "length": stats.pathLength})
else:
self.notify({"type": "path_not_found", "strategy": stats.algorithm})
self.notify({"type": "search_finished", "stats": stats})
return stats

View File

@ -0,0 +1,45 @@
import heapq
from strategies.pathfinding_strategy import PathFindingStrategy
class AStarStrategy(PathFindingStrategy):
name = "A*"
def heuristic(self, cell, exitCell):
return abs(cell.x - exitCell.x) + abs(cell.y - exitCell.y)
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
open_set = []
heapq.heappush(open_set, (0, 0, start.x, start.y, start))
parent = {}
g_score = {(start.x, start.y): 0}
closed = set()
while open_set:
f_score, current_g, _, _, current = heapq.heappop(open_set)
pos = (current.x, current.y)
if pos in closed:
continue
closed.add(pos)
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
for neighbor in maze.getNeighbors(current):
npos = (neighbor.x, neighbor.y)
tentative_g = current_g + getattr(neighbor, "weight", 1)
if tentative_g < g_score.get(npos, float("inf")):
g_score[npos] = tentative_g
parent[npos] = current
new_f = tentative_g + self.heuristic(neighbor, exitCell)
heapq.heappush(open_set, (new_f, tentative_g, neighbor.x, neighbor.y, neighbor))
return []

View File

@ -0,0 +1,31 @@
from collections import deque
from strategies.pathfinding_strategy import PathFindingStrategy
class BFSStrategy(PathFindingStrategy):
name = "BFS"
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
queue = deque([start])
visited = {(start.x, start.y)}
parent = {}
while queue:
current = queue.popleft()
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
for neighbor in maze.getNeighbors(current):
pos = (neighbor.x, neighbor.y)
if pos not in visited:
visited.add(pos)
parent[pos] = current
queue.append(neighbor)
return []

View File

@ -0,0 +1,35 @@
from strategies.pathfinding_strategy import PathFindingStrategy
class DFSStrategy(PathFindingStrategy):
name = "DFS"
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
stack = [start]
visited = set()
parent = {}
while stack:
current = stack.pop()
pos = (current.x, current.y)
if pos in visited:
continue
visited.add(pos)
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
neighbors = maze.getNeighbors(current)
for neighbor in reversed(neighbors):
npos = (neighbor.x, neighbor.y)
if npos not in visited:
parent[npos] = current
stack.append(neighbor)
return []

View File

@ -0,0 +1,41 @@
import heapq
from strategies.pathfinding_strategy import PathFindingStrategy
class DijkstraStrategy(PathFindingStrategy):
name = "Dijkstra"
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
pq = [(0, start.x, start.y, start)]
dist = {(start.x, start.y): 0}
parent = {}
closed = set()
while pq:
current_cost, _, _, current = heapq.heappop(pq)
pos = (current.x, current.y)
if pos in closed:
continue
closed.add(pos)
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
for neighbor in maze.getNeighbors(current):
npos = (neighbor.x, neighbor.y)
step_cost = getattr(neighbor, "weight", 1)
new_cost = current_cost + step_cost
if new_cost < dist.get(npos, float("inf")):
dist[npos] = new_cost
parent[npos] = current
heapq.heappush(pq, (new_cost, neighbor.x, neighbor.y, neighbor))
return []

View File

@ -0,0 +1,30 @@
from abc import ABC, abstractmethod
class PathFindingStrategy(ABC):
name = "Base"
def __init__(self):
self.visitedCount = 0
@abstractmethod
def findPath(self, maze, start, exitCell):
raise NotImplementedError
def _restore_path(self, parent, start, exitCell):
if exitCell is None or start is None:
return []
path = []
current = exitCell
while True:
path.append(current)
if current.x == start.x and current.y == start.y:
break
current = parent.get((current.x, current.y))
if current is None:
return []
path.reverse()
return path