From 8124c755f8f6d4e65aa4be95e60e0aec9f7bc738 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Fri, 20 Mar 2026 20:06:29 +0300 Subject: [PATCH 01/24] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=88=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task-1/BinaryTree.py | 20 ++++++++++++++++++++ MusinAA/task-1/HashTable.py | 18 ++++++++++++++++++ MusinAA/task-1/LinkedList.py | 25 +++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 MusinAA/task-1/BinaryTree.py create mode 100644 MusinAA/task-1/HashTable.py create mode 100644 MusinAA/task-1/LinkedList.py diff --git a/MusinAA/task-1/BinaryTree.py b/MusinAA/task-1/BinaryTree.py new file mode 100644 index 00000000..aa39c0ed --- /dev/null +++ b/MusinAA/task-1/BinaryTree.py @@ -0,0 +1,20 @@ +""" +Двоичное дерево поиска + +Узел — словарь: +{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}. +""" + +def bst_insert(root, name, phone): + """Рекурсивно или итеративно вставляет, + возвращает новый корень (если корень меняется).""" + +def bst_find(root, name): + ... + +def bst_delete(root, name): + """Удаление, возвращает новый корень.""" + +def bst_list_all(root): + """Центрированный обход. + Рекурсивно собирает записи в отсортированном порядке.""" \ No newline at end of file diff --git a/MusinAA/task-1/HashTable.py b/MusinAA/task-1/HashTable.py new file mode 100644 index 00000000..8fb62880 --- /dev/null +++ b/MusinAA/task-1/HashTable.py @@ -0,0 +1,18 @@ +""" +Хеш-таблица + +Хранится как список buckets фиксированной длины, +каждый элемент — голова связного списка (или None). +""" + +def ht_insert(buckets, name, phone): + """вычисляет индекс, вызывает ll_insert для соответствующего бакета.""" + +def ht_find(buckets, name): + ... + +def ht_delete(buckets, name): + ... + +def ht_list_all(buckets): + """Собирает все записи из всех бакетов и сортирует""" \ No newline at end of file diff --git a/MusinAA/task-1/LinkedList.py b/MusinAA/task-1/LinkedList.py new file mode 100644 index 00000000..c9dd29fa --- /dev/null +++ b/MusinAA/task-1/LinkedList.py @@ -0,0 +1,25 @@ +""" +Связный список (LinkedListPhoneBook) + +Узел представляется словарём: +{'name': 'Имя', 'phone': '123', 'next': None}. +""" + + + +def ll_insert(head, name, phone): + """ + Проходит до конца (или сразу добавляет в конец) и возвращает новую + голову (если вставка в начало) или изменяет список по ссылке. + Удобнее возвращать новую голову, если вставка может быть в начало. + """ + +def ll_find(head, name): + """Ищет узел, возвращает телефон или None.""" + +def ll_delete(head, name): + """Удаляет узел, возвращает новую голову.""" + +def ll_list_all(head): + """Cобирает все записи в список и сортирует. + сортировка вынесена отдельно).""" \ No newline at end of file From 9c1cd94e872e58d3902c34c4a4941d6671d06471 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Fri, 20 Mar 2026 22:01:33 +0300 Subject: [PATCH 02/24] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20.gitignore=20=D0=B4=D0=BB=D1=8F=20vscode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 MusinAA/.gitignore diff --git a/MusinAA/.gitignore b/MusinAA/.gitignore new file mode 100644 index 00000000..1d74e219 --- /dev/null +++ b/MusinAA/.gitignore @@ -0,0 +1 @@ +.vscode/ From b984ec3569ecac0072c123e045274545aa903d82 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Fri, 20 Mar 2026 22:04:02 +0300 Subject: [PATCH 03/24] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D1=8B=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20LinkedList?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task-1/LinkedList.py | 25 -------- MusinAA/task1/__init__.py | 0 .../structures}/BinaryTree.py | 0 .../{task-1 => task1/structures}/HashTable.py | 0 MusinAA/task1/structures/LinkedList.py | 60 +++++++++++++++++++ MusinAA/task1/structures/__init__.py | 0 6 files changed, 60 insertions(+), 25 deletions(-) delete mode 100644 MusinAA/task-1/LinkedList.py create mode 100644 MusinAA/task1/__init__.py rename MusinAA/{task-1 => task1/structures}/BinaryTree.py (100%) rename MusinAA/{task-1 => task1/structures}/HashTable.py (100%) create mode 100644 MusinAA/task1/structures/LinkedList.py create mode 100644 MusinAA/task1/structures/__init__.py diff --git a/MusinAA/task-1/LinkedList.py b/MusinAA/task-1/LinkedList.py deleted file mode 100644 index c9dd29fa..00000000 --- a/MusinAA/task-1/LinkedList.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Связный список (LinkedListPhoneBook) - -Узел представляется словарём: -{'name': 'Имя', 'phone': '123', 'next': None}. -""" - - - -def ll_insert(head, name, phone): - """ - Проходит до конца (или сразу добавляет в конец) и возвращает новую - голову (если вставка в начало) или изменяет список по ссылке. - Удобнее возвращать новую голову, если вставка может быть в начало. - """ - -def ll_find(head, name): - """Ищет узел, возвращает телефон или None.""" - -def ll_delete(head, name): - """Удаляет узел, возвращает новую голову.""" - -def ll_list_all(head): - """Cобирает все записи в список и сортирует. - сортировка вынесена отдельно).""" \ No newline at end of file diff --git a/MusinAA/task1/__init__.py b/MusinAA/task1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MusinAA/task-1/BinaryTree.py b/MusinAA/task1/structures/BinaryTree.py similarity index 100% rename from MusinAA/task-1/BinaryTree.py rename to MusinAA/task1/structures/BinaryTree.py diff --git a/MusinAA/task-1/HashTable.py b/MusinAA/task1/structures/HashTable.py similarity index 100% rename from MusinAA/task-1/HashTable.py rename to MusinAA/task1/structures/HashTable.py diff --git a/MusinAA/task1/structures/LinkedList.py b/MusinAA/task1/structures/LinkedList.py new file mode 100644 index 00000000..54850adc --- /dev/null +++ b/MusinAA/task1/structures/LinkedList.py @@ -0,0 +1,60 @@ +""" +Связный список (LinkedListPhoneBook) + +Узел представляется словарём: +{'name': 'Имя', 'phone': '123', 'next': None}. +""" + + +def ll_insert(head : dict|None, name: str, phone: str) -> dict: + """ + Проходит до конца (или сразу добавляет в конец) и возвращает новую + голову (если вставка в начало) или изменяет список по ссылке. + Удобнее возвращать новую голову, если вставка может быть в начало. + """ + + newNode = {'name': name, 'phone': phone, 'next': None} + if head == None: + return newNode + + currentNode = head + while currentNode['next'] != None: + currentNode = currentNode['next'] + currentNode['next'] = newNode + return head + +def ll_find(head : dict|None, name: str) -> str|None: + """Ищет узел, возвращает телефон или None.""" + currentNode = head + while currentNode != None: + if currentNode['name'] == name: + return currentNode['phone'] + currentNode = currentNode['next'] + return None + +def ll_delete(head : dict|None, name: str) -> dict: + """Удаляет узел, возвращает новую голову.""" + if head == None: + return None + + if head['name'] == name: + return head['next'] + + currentNode = head + while currentNode['next'] != None: + if currentNode['next']['name'] == name: + currentNode['next'] = currentNode['next']['next'] + return head + currentNode = currentNode['next'] + return head + +def ll_list_all(head: dict|None) -> list: + """Cобирает все записи в список и сортирует. + сортировка вынесена отдельно).""" + records = [] + currentNode = head + while currentNode != None: + records.append((currentNode['name'], currentNode['phone'])) + currentNode = currentNode['next'] + records.sort(key=lambda item: item[0]) + return records \ No newline at end of file diff --git a/MusinAA/task1/structures/__init__.py b/MusinAA/task1/structures/__init__.py new file mode 100644 index 00000000..e69de29b From 0b092e8d26c4014d7df17dff378f219aa49ddcd1 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Fri, 20 Mar 2026 22:05:07 +0300 Subject: [PATCH 04/24] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D1=91=D0=BD=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/MusinAA/.gitignore b/MusinAA/.gitignore index 1d74e219..cb48c0c2 100644 --- a/MusinAA/.gitignore +++ b/MusinAA/.gitignore @@ -1 +1,2 @@ .vscode/ +*/tests/ \ No newline at end of file From 9bdefcc922868f1ffa1ddbb55ed718c8b086d1a9 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sat, 21 Mar 2026 23:41:46 +0300 Subject: [PATCH 05/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20HashTable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task1/structures/HashTable.py | 54 +++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/MusinAA/task1/structures/HashTable.py b/MusinAA/task1/structures/HashTable.py index 8fb62880..81b4d905 100644 --- a/MusinAA/task1/structures/HashTable.py +++ b/MusinAA/task1/structures/HashTable.py @@ -5,14 +5,54 @@ каждый элемент — голова связного списка (или None). """ -def ht_insert(buckets, name, phone): - """вычисляет индекс, вызывает ll_insert для соответствующего бакета.""" +from task1.structures.LinkedList import * -def ht_find(buckets, name): - ... +def hash_fun(name: str, size: int) -> int: + """Принимает имя и возвращает индекс бакета для него.""" + if size <= 0: + raise ValueError("size должен быть больше 0") + + hashSum = 0 + n = size+1 + base = 1103 # ord('я') + for letter in name: + hashSum += ord(letter) * pow(base, n) + n -= 1 + return int(hashSum) % size -def ht_delete(buckets, name): - ... +def ht_insert(buckets: list, name: str, phone: str) -> list: + """Возвращает новый массив бакетов + Вычисляет индекс, вызывает ll_insert для соответствующего бакета. + Функция не меняет размер массива бакетов автоматически!""" + if buckets == []: + raise ValueError("Длинна buckets должна быть больше 0") + + size = len(buckets) + index = hash_fun(name, size) + buckets[index] = ll_insert(buckets[index], name, phone) + return buckets + +def ht_delete(buckets: list, name: str) -> list: + """Возвращает новый массив бакетов без элемента с именем name""" + if buckets == []: + raise ValueError("Длинна buckets должна быть больше 0") + + size = len(buckets) + index = hash_fun(name, size) + buckets[index] = ll_delete(buckets[index], name) + return buckets + +def ht_find(buckets: list, name: str) -> str: + if buckets == []: + raise ValueError("Длинна buckets должна быть больше 0") + + size = len(buckets) + index = hash_fun(name, size) + return ll_find(buckets[index], name) def ht_list_all(buckets): - """Собирает все записи из всех бакетов и сортирует""" \ No newline at end of file + """Собирает все записи из всех бакетов и сортирует""" + allRecords = [] + for bucket in buckets: + allRecords.extend(ll_list_all(bucket)) + return sorted(allRecords, key=lambda x: x[0]) \ No newline at end of file From b383ed1cdc1e8b777a49e6d7121ddc26d3044235 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sun, 22 Mar 2026 00:39:01 +0300 Subject: [PATCH 06/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20bst=5Finsert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task1/structures/BinaryTree.py | 35 ++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/MusinAA/task1/structures/BinaryTree.py b/MusinAA/task1/structures/BinaryTree.py index aa39c0ed..f2e4aad2 100644 --- a/MusinAA/task1/structures/BinaryTree.py +++ b/MusinAA/task1/structures/BinaryTree.py @@ -5,16 +5,37 @@ {'name': 'Имя', 'phone': '123', 'left': None, 'right': None}. """ -def bst_insert(root, name, phone): - """Рекурсивно или итеративно вставляет, - возвращает новый корень (если корень меняется).""" +def bst_insert(root: dict|None, name: str, phone: str) -> dict: + """Итеративно вставляет, возвращает новый корень (если корень меняется).""" + if root == None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + + # '674' < '722' == True, lol + current = root + while True: + if current['name'] == name: + current['phone'] == phone + elif current['name'] < name: + if current['left'] == None: + current['left'] = bst_insert(None, name, phone) + return root + else: + current = current['left'] + else: + if current['right'] == None: + current['right'] = bst_insert(None, name, phone) + return root + else: + current = current['right'] + # Увы, это самый лаконичный вариант, который я придумал. -def bst_find(root, name): - ... -def bst_delete(root, name): +def bst_find(root: dict|None, name: str) -> str|None: + """Поиск в ширину.""" + +def bst_delete(root: dict, name: str) -> dict: """Удаление, возвращает новый корень.""" -def bst_list_all(root): +def bst_list_all(root: dict) -> list: """Центрированный обход. Рекурсивно собирает записи в отсортированном порядке.""" \ No newline at end of file From dd4eca84077590089079cf82a52c1fd103ed6cb8 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Mon, 23 Mar 2026 00:07:17 +0300 Subject: [PATCH 07/24] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=D1=8E=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=20BinaryTree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task1/structures/BinaryTree.py | 57 +++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/MusinAA/task1/structures/BinaryTree.py b/MusinAA/task1/structures/BinaryTree.py index f2e4aad2..93493f57 100644 --- a/MusinAA/task1/structures/BinaryTree.py +++ b/MusinAA/task1/structures/BinaryTree.py @@ -14,8 +14,9 @@ def bst_insert(root: dict|None, name: str, phone: str) -> dict: current = root while True: if current['name'] == name: - current['phone'] == phone - elif current['name'] < name: + current['phone'] = phone + return root + elif name < current['name']: if current['left'] == None: current['left'] = bst_insert(None, name, phone) return root @@ -32,10 +33,56 @@ def bst_insert(root: dict|None, name: str, phone: str) -> dict: def bst_find(root: dict|None, name: str) -> str|None: """Поиск в ширину.""" + node = find_node_to_delete(root, name) + if node != None: + return node['phone'] + +def find_node_to_delete(root: dict, name: str) -> dict|None: + """Поиск в ширину.""" + while root != None: + if root['name'] == name: + return root + elif name < root['name']: + root = root['left'] + else: + root = root['right'] + return None + +def find_minimal_child(root: dict) -> dict|None: + while root['left']: + root = root['left'] + return root + +def bst_delete(root: dict, name: str) -> None: + """Удаляет узел и возвращает новый корень.""" + if root is None: + return None + + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + else: + # Случай 1: нет детей или один ребенок + if root['left'] is None: + return root['right'] + elif root['right'] is None: + return root['left'] + + # Случай 2: два ребенка + min_node = find_minimal_child(root['right']) + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) + + return root -def bst_delete(root: dict, name: str) -> dict: - """Удаление, возвращает новый корень.""" def bst_list_all(root: dict) -> list: """Центрированный обход. - Рекурсивно собирает записи в отсортированном порядке.""" \ No newline at end of file + Рекурсивно собирает записи в отсортированном порядке.""" + + if root is None: + return [] + node_values = {"name": root['name'], "phone": root['phone']} + return bst_list_all(root['left']) + [node_values] + bst_list_all(root['right']) \ No newline at end of file From 7026ad395d157f2917698dd00ea7bc186019be60 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Mon, 30 Mar 2026 17:47:24 +0300 Subject: [PATCH 08/24] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D1=82=20=D0=BE?= =?UTF-8?q?=D1=82=D1=87=D1=91=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/docs/Report 1.ipynb | 126 ++++++++++++++++++++++++++ MusinAA/task1/structures/HashTable.py | 7 +- MusinAA/task1/util/__init__.py | 0 MusinAA/task1/util/randomNames.py | 47 ++++++++++ MusinAA/task1/util/timeTester.py | 24 +++++ 5 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 MusinAA/docs/Report 1.ipynb create mode 100644 MusinAA/task1/util/__init__.py create mode 100644 MusinAA/task1/util/randomNames.py create mode 100644 MusinAA/task1/util/timeTester.py diff --git a/MusinAA/docs/Report 1.ipynb b/MusinAA/docs/Report 1.ipynb new file mode 100644 index 00000000..cd21dda2 --- /dev/null +++ b/MusinAA/docs/Report 1.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2acfa743", + "metadata": {}, + "source": [ + "# 0. Подготовим окружение" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4689b73e", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "sys.path.insert(0, os.path.abspath( '../task1'))\n", + "sys.path.insert(0, os.path.abspath( '../'))" + ] + }, + { + "cell_type": "markdown", + "id": "37cc11a5", + "metadata": {}, + "source": [ + "# 1. Генерация тестовых данных\n", + "\n", + "Создадим список records из N=10000 элементов. Каждый элемент — кортеж (name, phone). \n", + "Имена возъмём случайные из небольшого набора (чтобы были повторения и коллизии). \n", + "Для проверки влияния порядка подготовим два варианта: \n", + "\n", + "_records_shuffled_ — случайный порядок. \n", + "_records_sorted_ — отсортированный по имени (по алфавиту)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7d073d06", + "metadata": {}, + "outputs": [], + "source": [ + "from util.randomNames import generate_test_data\n", + "records_shuffled = generate_test_data(N=10000)\n", + "records_sorted = generate_test_data(N=10000, _sorted=True)\n", + "\n", + "from util.timeTester import insert_tester" + ] + }, + { + "cell_type": "markdown", + "id": "c2f4989c", + "metadata": {}, + "source": [ + "# 2. Проведение замеров\n", + "### A. Время вставки" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eba5888c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Function': 'll_insert', 'shuffled': 4.4217493799999374, 'sorted': 5.294112365000046}\n", + "{'Function': 'ht_insert', 'shuffled': 0.338659952999933, 'sorted': 0.20052070199994887}\n", + "{'Function': 'bst_insert', 'shuffled': 0.01834327899996424, 'sorted': 0.18432242999983828}\n" + ] + } + ], + "source": [ + "from structures.LinkedList import *\n", + "from structures.HashTable import *\n", + "from structures.BinaryTree import *\n", + "\n", + "print(insert_tester(ll_insert, records_shuffled, records_sorted))\n", + "print(insert_tester(ht_insert, records_shuffled, records_sorted))\n", + "print(insert_tester(bst_insert, records_shuffled, records_sorted))" + ] + }, + { + "cell_type": "markdown", + "id": "383c4b1b", + "metadata": {}, + "source": [ + "### Б. Поиск 100 случайных записей и 10 несуществующих" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1acfa50", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/MusinAA/task1/structures/HashTable.py b/MusinAA/task1/structures/HashTable.py index 81b4d905..08b93fd5 100644 --- a/MusinAA/task1/structures/HashTable.py +++ b/MusinAA/task1/structures/HashTable.py @@ -20,12 +20,13 @@ def hash_fun(name: str, size: int) -> int: n -= 1 return int(hashSum) % size -def ht_insert(buckets: list, name: str, phone: str) -> list: +def ht_insert(buckets: list, name: str, phone: str, blen:int = 50) -> list: """Возвращает новый массив бакетов Вычисляет индекс, вызывает ll_insert для соответствующего бакета. Функция не меняет размер массива бакетов автоматически!""" - if buckets == []: - raise ValueError("Длинна buckets должна быть больше 0") + if buckets == [] or buckets == None: + buckets = [None] * blen + # raise ValueError("Длинна buckets должна быть больше 0") size = len(buckets) index = hash_fun(name, size) diff --git a/MusinAA/task1/util/__init__.py b/MusinAA/task1/util/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MusinAA/task1/util/randomNames.py b/MusinAA/task1/util/randomNames.py new file mode 100644 index 00000000..3fffb678 --- /dev/null +++ b/MusinAA/task1/util/randomNames.py @@ -0,0 +1,47 @@ +import random + +names_pool = ( + "Иван", "Мария", "Петр", "Анна", "Сергей", "Елена", "Алексей", "Ольга", + "Дмитрий", "Татьяна", "Михаил", "Наталья", "Андрей", "Ирина", "Николай", + "Светлана", "Владимир", "Екатерина", "Александр", "Юлия", "Павел", "Ксения", + "Виктор", "Анастасия", "Артем", "Виктория", "Максим", "Полина", "Даниил", + "София", "Евгений", "Алиса", "Станислав", "Дарья", "Георгий", "Вероника", + "Кирилл", "Маргарита", "Тимофей", "Арина", "Руфина", "Илларион", "Стелла", + "Роман", "Валерия", "Игорь", "Алина", "Олег", "Диана", "Юрий", "Милана", + "Василий", "Ева", "Никита", "Алиса", "Константин", "Кира", "Денис", "Ангелина", + "Вячеслав", "Мирослава", "Григорий", "Эмилия", "Леонид", "Василиса", "Руслан", + "Стефания", "Арсений", "Есения", "Антон", "Яна", "Матвей", "Любовь", "Семен", + "Надежда", "Федор", "Софья", "Лев", "Варвара", "Егор", "Амелия", "Борис", + "Агата", "Захар", "Камилла", "Давид", "Олеся", "Ярослав", "Людмила", "Данила", + "Регина", "Марк", "Каролина", "Артур", "Нелли", "Глеб", "Инна", "Платон", + "Нина", "Святослав", "Римма", "Родион", "Лидия", "Эдуард", "Жанна", "Вадим", + "Рената", "Савелий", "Алла", "Назар", "Снежана", "Демид", "Лариса", "Филипп", + "Злата", "Тимур", "Майя", "Клим", "Эльвира", "Дамир", "Таисия", "Илья", + "Роза", "Виталий", "Азалия", "Степан", "Лиана", "Богдан", "Инесса", "Эрик", + "Ариана", "Алан", "Юлиана", "Лука", "Антонина", "Мирон", "Клавдия", "Гордей", + "Руслана", "Макар", "Елизавета", "Северин", "Александра", "Моисей", "Агафья", + "Наум", "Серафима", "Влад", "Фаина", "Кузьма", "Пелагея", "Ермак", "Ульяна", + "Тарас", "Марианна", "Остап", "Бронислава", "Архип", "Владислава", "Фома", + "Станислава", "Еремей", "Зинаида", "Прохор", "Раиса", "Мстислав", "Галина", + "Ростислав", "Валентина", "Серафим", "Евдокия", "Лаврентий", "Кристина", + "Никон", "Анфиса", "Феликс", "Лия", "Иннокентий", "Роксана", "Всеволод", + "Эвелина", "Модест", "Юнона", "Трофим", "Изабелла", "Аполлон", "Глория", + "Касьян", "Аврора", "Любомир", "Адель", "Бронислав", "Доминика", "Афанасий", + "Фрида", "Евстафий", "Ассоль", "Венедикт", "Цветана", "Епифан", "Мелисса", + "Добрыня" +) + +def generate_phone(phone_len=11) -> str: + # 88005553535 + return str(random.randint(10**phone_len, 10**(phone_len+1)-1)) + +def generate_test_data(N=10000, _sorted=False): + records = [] + for i in range(N): + name = random.choice(names_pool) + phone = generate_phone() + records.append((name, phone)) + + if _sorted: + return sorted(records) + return records \ No newline at end of file diff --git a/MusinAA/task1/util/timeTester.py b/MusinAA/task1/util/timeTester.py new file mode 100644 index 00000000..a0a5007d --- /dev/null +++ b/MusinAA/task1/util/timeTester.py @@ -0,0 +1,24 @@ +import time +from typing import Callable, Any + +def _concrete_insert_tester(func: Callable[[Any, str, str], Any], records: list) -> float: + """Исследует время работы функции вставки""" + aboba = None + + start = time.perf_counter() + for item in records: + aboba = func(aboba, name=item[0], phone=item[1]) + end = time.perf_counter() + + elapsed = end - start + return elapsed + +def insert_tester(func_to_test: Callable[[Any, str, str], Any], records_shuffled, records_sorted) -> dict: + """Возвращает словарь с временем сортировки в обоих режимах""" + shuffled = _concrete_insert_tester(func_to_test, records_shuffled) + sorted = _concrete_insert_tester(func_to_test, records_sorted) + + return {"Function": func_to_test.__name__, + "shuffled": shuffled, + "sorted": sorted + } \ No newline at end of file From 94bc3b8524a5b813fb1ec44501a5293278bdbf67 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Fri, 3 Apr 2026 01:30:35 +0300 Subject: [PATCH 09/24] Pylance fixes --- MusinAA/task1/structures/BinaryTree.py | 2 +- MusinAA/task1/structures/HashTable.py | 6 ++-- MusinAA/task1/structures/LinkedList.py | 2 +- MusinAA/task1/util/randomNames.py | 8 +++++ MusinAA/task1/util/timeTester.py | 44 ++++++++++++++++---------- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/MusinAA/task1/structures/BinaryTree.py b/MusinAA/task1/structures/BinaryTree.py index 93493f57..e98c31a7 100644 --- a/MusinAA/task1/structures/BinaryTree.py +++ b/MusinAA/task1/structures/BinaryTree.py @@ -37,7 +37,7 @@ def bst_find(root: dict|None, name: str) -> str|None: if node != None: return node['phone'] -def find_node_to_delete(root: dict, name: str) -> dict|None: +def find_node_to_delete(root: dict|None, name: str) -> dict|None: """Поиск в ширину.""" while root != None: if root['name'] == name: diff --git a/MusinAA/task1/structures/HashTable.py b/MusinAA/task1/structures/HashTable.py index 08b93fd5..dd165999 100644 --- a/MusinAA/task1/structures/HashTable.py +++ b/MusinAA/task1/structures/HashTable.py @@ -20,7 +20,7 @@ def hash_fun(name: str, size: int) -> int: n -= 1 return int(hashSum) % size -def ht_insert(buckets: list, name: str, phone: str, blen:int = 50) -> list: +def ht_insert(buckets: list|None, name: str, phone: str, blen:int = 50) -> list: """Возвращает новый массив бакетов Вычисляет индекс, вызывает ll_insert для соответствующего бакета. Функция не меняет размер массива бакетов автоматически!""" @@ -43,8 +43,8 @@ def ht_delete(buckets: list, name: str) -> list: buckets[index] = ll_delete(buckets[index], name) return buckets -def ht_find(buckets: list, name: str) -> str: - if buckets == []: +def ht_find(buckets: list|None, name: str) -> str|None: + if buckets == [] or buckets == None: raise ValueError("Длинна buckets должна быть больше 0") size = len(buckets) diff --git a/MusinAA/task1/structures/LinkedList.py b/MusinAA/task1/structures/LinkedList.py index 54850adc..ed0d1ba5 100644 --- a/MusinAA/task1/structures/LinkedList.py +++ b/MusinAA/task1/structures/LinkedList.py @@ -32,7 +32,7 @@ def ll_find(head : dict|None, name: str) -> str|None: currentNode = currentNode['next'] return None -def ll_delete(head : dict|None, name: str) -> dict: +def ll_delete(head : dict|None, name: str) -> dict|None: """Удаляет узел, возвращает новую голову.""" if head == None: return None diff --git a/MusinAA/task1/util/randomNames.py b/MusinAA/task1/util/randomNames.py index 3fffb678..2e08f82b 100644 --- a/MusinAA/task1/util/randomNames.py +++ b/MusinAA/task1/util/randomNames.py @@ -31,6 +31,14 @@ names_pool = ( "Добрыня" ) +_non_existent_names = [ + "Ноль", "Целковый", "Полушка", "Четвертушка", "Осьмушка", + "Пудовичок", "Медячок", "Серебрячок", "Золотничок", "Девятичок" +] +assert set(names_pool).isdisjoint(set(_non_existent_names)), \ +"В списке несуществующих имён существуют существующие имена сущностей" +names_pool_to_find = random.choices(names_pool, k=100) + _non_existent_names + def generate_phone(phone_len=11) -> str: # 88005553535 return str(random.randint(10**phone_len, 10**(phone_len+1)-1)) diff --git a/MusinAA/task1/util/timeTester.py b/MusinAA/task1/util/timeTester.py index a0a5007d..763b25df 100644 --- a/MusinAA/task1/util/timeTester.py +++ b/MusinAA/task1/util/timeTester.py @@ -1,24 +1,36 @@ import time from typing import Callable, Any +from task1.util.randomNames import names_pool_to_find -def _concrete_insert_tester(func: Callable[[Any, str, str], Any], records: list) -> float: - """Исследует время работы функции вставки""" - aboba = None - +def test(records: list, + insert_func: Callable[[Any, str, str], Any], + find_func: Callable[[Any, str], Any], + delete_func: Callable[[Any, str], Any]) -> dict: + data = None + + # Вставка всех записей start = time.perf_counter() for item in records: - aboba = func(aboba, name=item[0], phone=item[1]) + data = insert_func(data, item[0], item[1]) end = time.perf_counter() + insert_time = end - start - elapsed = end - start - return elapsed + # Поиск 110 случайных записей + start = time.perf_counter() + for name in names_pool_to_find: + find_func(data, name) + end = time.perf_counter() + find_time = end - start -def insert_tester(func_to_test: Callable[[Any, str, str], Any], records_shuffled, records_sorted) -> dict: - """Возвращает словарь с временем сортировки в обоих режимах""" - shuffled = _concrete_insert_tester(func_to_test, records_shuffled) - sorted = _concrete_insert_tester(func_to_test, records_sorted) - - return {"Function": func_to_test.__name__, - "shuffled": shuffled, - "sorted": sorted - } \ No newline at end of file + # Удаление 50 случайных записей + start = time.perf_counter() + for name in names_pool_to_find: + data = delete_func(data, name) + end = time.perf_counter() + delete_time = end - start + + return { + "insert_time" : insert_time , + "find_time" : find_time , + "delete_time": delete_time + } From cb02fb20feb303c32bdf70b9b7cc94179c5d7623 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Fri, 3 Apr 2026 01:31:26 +0300 Subject: [PATCH 10/24] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=BD=20=D0=B8=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BF=D0=BE=D0=B4=D1=81=D1=87?= =?UTF-8?q?=D1=91=D1=82=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=B0=D0=B6=D0=B4=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=A1=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/docs/Report 1.ipynb | 84 ++++++++++++++++++++--------------- MusinAA/docs/data/results.csv | 31 +++++++++++++ 2 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 MusinAA/docs/data/results.csv diff --git a/MusinAA/docs/Report 1.ipynb b/MusinAA/docs/Report 1.ipynb index cd21dda2..3e19ef8b 100644 --- a/MusinAA/docs/Report 1.ipynb +++ b/MusinAA/docs/Report 1.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 16, "id": "4689b73e", "metadata": {}, "outputs": [], @@ -38,16 +38,16 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "7d073d06", + "execution_count": 17, + "id": "a3b5c31b", "metadata": {}, "outputs": [], "source": [ "from util.randomNames import generate_test_data\n", - "records_shuffled = generate_test_data(N=10000)\n", - "records_sorted = generate_test_data(N=10000, _sorted=True)\n", + "from util.timeTester import test\n", "\n", - "from util.timeTester import insert_tester" + "records_shuffled = generate_test_data(N=10000)\n", + "records_sorted = generate_test_data(N=10000, _sorted=True)" ] }, { @@ -55,51 +55,63 @@ "id": "c2f4989c", "metadata": {}, "source": [ - "# 2. Проведение замеров\n", - "### A. Время вставки" + "# 2. Проведение замеров" ] }, { "cell_type": "code", - "execution_count": null, - "id": "eba5888c", + "execution_count": 18, + "id": "df12d41d", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'Function': 'll_insert', 'shuffled': 4.4217493799999374, 'sorted': 5.294112365000046}\n", - "{'Function': 'ht_insert', 'shuffled': 0.338659952999933, 'sorted': 0.20052070199994887}\n", - "{'Function': 'bst_insert', 'shuffled': 0.01834327899996424, 'sorted': 0.18432242999983828}\n" - ] - } - ], + "outputs": [], "source": [ + "# Подготовим функции СД, которые будем тестировать\n", "from structures.LinkedList import *\n", "from structures.HashTable import *\n", "from structures.BinaryTree import *\n", "\n", - "print(insert_tester(ll_insert, records_shuffled, records_sorted))\n", - "print(insert_tester(ht_insert, records_shuffled, records_sorted))\n", - "print(insert_tester(bst_insert, records_shuffled, records_sorted))" - ] - }, - { - "cell_type": "markdown", - "id": "383c4b1b", - "metadata": {}, - "source": [ - "### Б. Поиск 100 случайных записей и 10 несуществующих" + "func_list = {\"Связанный список\" : (ll_insert, ll_find, ll_delete),\n", + " \"Хэш-таблица\" : (ht_insert, ht_find, ht_delete),\n", + " \"Бинарное дерево\" : (bst_insert, bst_find, bst_delete)}" ] }, { "cell_type": "code", - "execution_count": null, - "id": "d1acfa50", + "execution_count": 24, + "id": "cc8d0436", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "# Проведём замеры\n", + "report = [[\"Структура\", \"Режим\", \"Вставка\", \"Поиск\", \"Удаление\"]]\n", + "records = {\"Cлучайный\" : records_shuffled, \"Отсортированный\" : records_sorted}\n", + "\n", + "TEST_ITERATIONS_NUM = 5\n", + "\n", + "for _ in range(TEST_ITERATIONS_NUM):\n", + " for mode, data in records.items():\n", + " for struct_name, fns in func_list.items():\n", + " result = test(data, *fns)\n", + " row = [struct_name, mode,\n", + " result[\"insert_time\"],\n", + " result[\"find_time\"],\n", + " result[\"delete_time\"]]\n", + " report.append(row)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "2eedf056", + "metadata": {}, + "outputs": [], + "source": [ + "# Сохраним данные в csv\n", + "import csv\n", + "with open(\"data/results.csv\", \"w\", newline=\"\") as f:\n", + " writer = csv.writer(f)\n", + " writer.writerows(report)" + ] } ], "metadata": { @@ -118,7 +130,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.2" + "version": "3.14.3" } }, "nbformat": 4, diff --git a/MusinAA/docs/data/results.csv b/MusinAA/docs/data/results.csv new file mode 100644 index 00000000..f8fd074d --- /dev/null +++ b/MusinAA/docs/data/results.csv @@ -0,0 +1,31 @@ +Структура,Режим,Вставка,Поиск,Удаление +Связанный список,Cлучайный,6.184219556999778,0.01564759699977003,0.020917029999509396 +Хэш-таблица,Cлучайный,0.20728507099920535,0.0011867590001202188,0.0012845039991589147 +Бинарное дерево,Cлучайный,0.01599855899985414,0.00017333699997834628,0.0001880449999589473 +Связанный список,Отсортированный,6.1492630290003945,0.08403158500004793,0.11902903299960599 +Хэш-таблица,Отсортированный,0.1831843130003108,0.003145785999549844,0.0030832479997116025 +Бинарное дерево,Отсортированный,0.17353334099971107,0.0018611919995237258,0.001973905000340892 +Связанный список,Cлучайный,5.064191275000667,0.015435045000231185,0.021122407000802923 +Хэш-таблица,Cлучайный,0.21584205599992856,0.001062764999915089,0.0010387600004833075 +Бинарное дерево,Cлучайный,0.01586526800019783,0.0001650120002523181,0.0002011500000662636 +Связанный список,Отсортированный,5.008700298999429,0.08874143100001675,0.11785725200024899 +Хэш-таблица,Отсортированный,0.16122512399942934,0.0027618209996944643,0.0031808120002096985 +Бинарное дерево,Отсортированный,0.16625836200000776,0.0017326589995718678,0.0018288709998159902 +Связанный список,Cлучайный,4.973175217000062,0.016034526000112237,0.029878299000301922 +Хэш-таблица,Cлучайный,0.19947776500066539,0.0008432810000158497,0.0009618849999242229 +Бинарное дерево,Cлучайный,0.015627648000190675,0.00015909000012470642,0.00018263399942952674 +Связанный список,Отсортированный,4.7649637760005135,0.07843009399948642,0.11216894700010016 +Хэш-таблица,Отсортированный,0.16914973799975996,0.0028197709998494247,0.003400436000447371 +Бинарное дерево,Отсортированный,0.16913629200007563,0.00174245800008066,0.00184838800032594 +Связанный список,Cлучайный,5.070599788999971,0.023625830999662867,0.024334026999895286 +Хэш-таблица,Cлучайный,0.2566551750005601,0.001680571000179043,0.0019877810000252794 +Бинарное дерево,Cлучайный,0.02019279199976154,0.00019776400040427689,0.0002360259995839442 +Связанный список,Отсортированный,5.0148616579999725,0.08842415099934442,0.13560766399950808 +Хэш-таблица,Отсортированный,0.18670927000039228,0.0038891280000825645,0.0034846049993575434 +Бинарное дерево,Отсортированный,0.1774570309999035,0.0019291399994472158,0.0021798640000270098 +Связанный список,Cлучайный,5.373787051000363,0.021812238000165962,0.025861819999590807 +Хэш-таблица,Cлучайный,0.19457704500018735,0.00110557600055472,0.0010436189995743916 +Бинарное дерево,Cлучайный,0.015349283000432479,0.00016242600031546317,0.00017538100019010017 +Связанный список,Отсортированный,5.167020247999972,0.08513789999960863,0.12899004599967157 +Хэш-таблица,Отсортированный,0.18223923400000785,0.0032350740002584644,0.0037601450003421633 +Бинарное дерево,Отсортированный,0.1719607389995872,0.0017423979998056893,0.0018891839999923832 From 94a9cba182e11561f22180a4b77c0f3d001a7fc9 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sun, 5 Apr 2026 10:06:28 +0300 Subject: [PATCH 11/24] =?UTF-8?q?=D0=9F=D0=B0=D1=80=D0=B0=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/docs/Report 1.ipynb | 20 ++++++++--- MusinAA/docs/data/results.csv | 60 ++++++++++++++++---------------- MusinAA/task1/util/timeTester.py | 5 +-- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/MusinAA/docs/Report 1.ipynb b/MusinAA/docs/Report 1.ipynb index 3e19ef8b..3af91fe7 100644 --- a/MusinAA/docs/Report 1.ipynb +++ b/MusinAA/docs/Report 1.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 26, "id": "4689b73e", "metadata": {}, "outputs": [], @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 27, "id": "a3b5c31b", "metadata": {}, "outputs": [], @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 28, "id": "df12d41d", "metadata": {}, "outputs": [], @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 29, "id": "cc8d0436", "metadata": {}, "outputs": [], @@ -101,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 30, "id": "2eedf056", "metadata": {}, "outputs": [], @@ -112,6 +112,16 @@ " writer = csv.writer(f)\n", " writer.writerows(report)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8523ae48", + "metadata": {}, + "outputs": [], + "source": [ + "TODO проверить работает ли оно вообще" + ] } ], "metadata": { diff --git a/MusinAA/docs/data/results.csv b/MusinAA/docs/data/results.csv index f8fd074d..8f5e275b 100644 --- a/MusinAA/docs/data/results.csv +++ b/MusinAA/docs/data/results.csv @@ -1,31 +1,31 @@ Структура,Режим,Вставка,Поиск,Удаление -Связанный список,Cлучайный,6.184219556999778,0.01564759699977003,0.020917029999509396 -Хэш-таблица,Cлучайный,0.20728507099920535,0.0011867590001202188,0.0012845039991589147 -Бинарное дерево,Cлучайный,0.01599855899985414,0.00017333699997834628,0.0001880449999589473 -Связанный список,Отсортированный,6.1492630290003945,0.08403158500004793,0.11902903299960599 -Хэш-таблица,Отсортированный,0.1831843130003108,0.003145785999549844,0.0030832479997116025 -Бинарное дерево,Отсортированный,0.17353334099971107,0.0018611919995237258,0.001973905000340892 -Связанный список,Cлучайный,5.064191275000667,0.015435045000231185,0.021122407000802923 -Хэш-таблица,Cлучайный,0.21584205599992856,0.001062764999915089,0.0010387600004833075 -Бинарное дерево,Cлучайный,0.01586526800019783,0.0001650120002523181,0.0002011500000662636 -Связанный список,Отсортированный,5.008700298999429,0.08874143100001675,0.11785725200024899 -Хэш-таблица,Отсортированный,0.16122512399942934,0.0027618209996944643,0.0031808120002096985 -Бинарное дерево,Отсортированный,0.16625836200000776,0.0017326589995718678,0.0018288709998159902 -Связанный список,Cлучайный,4.973175217000062,0.016034526000112237,0.029878299000301922 -Хэш-таблица,Cлучайный,0.19947776500066539,0.0008432810000158497,0.0009618849999242229 -Бинарное дерево,Cлучайный,0.015627648000190675,0.00015909000012470642,0.00018263399942952674 -Связанный список,Отсортированный,4.7649637760005135,0.07843009399948642,0.11216894700010016 -Хэш-таблица,Отсортированный,0.16914973799975996,0.0028197709998494247,0.003400436000447371 -Бинарное дерево,Отсортированный,0.16913629200007563,0.00174245800008066,0.00184838800032594 -Связанный список,Cлучайный,5.070599788999971,0.023625830999662867,0.024334026999895286 -Хэш-таблица,Cлучайный,0.2566551750005601,0.001680571000179043,0.0019877810000252794 -Бинарное дерево,Cлучайный,0.02019279199976154,0.00019776400040427689,0.0002360259995839442 -Связанный список,Отсортированный,5.0148616579999725,0.08842415099934442,0.13560766399950808 -Хэш-таблица,Отсортированный,0.18670927000039228,0.0038891280000825645,0.0034846049993575434 -Бинарное дерево,Отсортированный,0.1774570309999035,0.0019291399994472158,0.0021798640000270098 -Связанный список,Cлучайный,5.373787051000363,0.021812238000165962,0.025861819999590807 -Хэш-таблица,Cлучайный,0.19457704500018735,0.00110557600055472,0.0010436189995743916 -Бинарное дерево,Cлучайный,0.015349283000432479,0.00016242600031546317,0.00017538100019010017 -Связанный список,Отсортированный,5.167020247999972,0.08513789999960863,0.12899004599967157 -Хэш-таблица,Отсортированный,0.18223923400000785,0.0032350740002584644,0.0037601450003421633 -Бинарное дерево,Отсортированный,0.1719607389995872,0.0017423979998056893,0.0018891839999923832 +Связанный список,Cлучайный,4.661078881000321,0.012941928999680385,0.020117076999667916 +Хэш-таблица,Cлучайный,0.18879180299973086,0.0014481220005109208,0.0012560499999381136 +Бинарное дерево,Cлучайный,0.01616830700004357,0.00016560199946979992,0.00016564300040045055 +Связанный список,Отсортированный,5.038275819999399,0.07141196100019442,0.1353478549999636 +Хэш-таблица,Отсортированный,0.1631836659998953,0.003129734999674838,0.003202291999514273 +Бинарное дерево,Отсортированный,0.16634428900033527,0.0017354540004816954,0.001874036000117485 +Связанный список,Cлучайный,4.664316974999565,0.014653709000413073,0.02280332600003021 +Хэш-таблица,Cлучайный,0.186777711999639,0.0010283499996148748,0.0009878339997158037 +Бинарное дерево,Cлучайный,0.016256722999969497,0.00015175599946815055,0.0001649409996389295 +Связанный список,Отсортированный,5.907060995999927,0.08458327099924645,0.12409427100010362 +Хэш-таблица,Отсортированный,0.18781050100005814,0.003518858999996155,0.0038270310005827923 +Бинарное дерево,Отсортированный,0.17500397699950554,0.0019018089997189236,0.001833940000324219 +Связанный список,Cлучайный,5.755159846999959,0.016742109000006167,0.026386416000605095 +Хэш-таблица,Cлучайный,0.22951744300007704,0.0009921219998432207,0.0011742059996322496 +Бинарное дерево,Cлучайный,0.01580532200023299,0.00017768500038073398,0.00019722199976968113 +Связанный список,Отсортированный,6.30150953500015,0.11176149099992472,0.1474957850005012 +Хэш-таблица,Отсортированный,0.18557919999966543,0.003155624000100943,0.002713390000280924 +Бинарное дерево,Отсортированный,0.19082545899982506,0.001985636999961571,0.002389950000178942 +Связанный список,Cлучайный,6.395210606000546,0.014804241999627266,0.02304338900012226 +Хэш-таблица,Cлучайный,0.1902105980007036,0.000852088000101503,0.0009626670007492066 +Бинарное дерево,Cлучайный,0.01663886400001502,0.00016400000004068715,0.000184548000106588 +Связанный список,Отсортированный,4.850914527999521,0.0771323629996914,0.1152741280002374 +Хэш-таблица,Отсортированный,0.17607759100064868,0.002924628000073426,0.0033850670006358996 +Бинарное дерево,Отсортированный,0.19345043999965128,0.0018585970001367969,0.0019752460002564476 +Связанный список,Cлучайный,4.803303787999539,0.015972447000422108,0.0223228390004806 +Хэш-таблица,Cлучайный,0.19020581800032232,0.0011616620004133438,0.0009839170006671338 +Бинарное дерево,Cлучайный,0.016469425000650517,0.000160212000082538,0.00017693399968266021 +Связанный список,Отсортированный,4.741838529000233,0.075463203000254,0.10462550600004761 +Хэш-таблица,Отсортированный,0.16722737300005974,0.004002030999799899,0.005207103999964602 +Бинарное дерево,Отсортированный,0.16575109800032806,0.0020921670002280734,0.002277146999404067 diff --git a/MusinAA/task1/util/timeTester.py b/MusinAA/task1/util/timeTester.py index 763b25df..3e3204e6 100644 --- a/MusinAA/task1/util/timeTester.py +++ b/MusinAA/task1/util/timeTester.py @@ -1,6 +1,7 @@ import time +import random from typing import Callable, Any -from task1.util.randomNames import names_pool_to_find +from task1.util.randomNames import names_pool_to_find, names_pool def test(records: list, insert_func: Callable[[Any, str, str], Any], @@ -24,7 +25,7 @@ def test(records: list, # Удаление 50 случайных записей start = time.perf_counter() - for name in names_pool_to_find: + for name in random.choices(names_pool, k = 50): data = delete_func(data, name) end = time.perf_counter() delete_time = end - start From cd62d229a507992fa3a9b4558e40aa90f1ffdd78 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sat, 11 Apr 2026 14:23:15 +0300 Subject: [PATCH 12/24] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D0=BE=D0=BD=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BE=D1=82=D1=87=D1=91=D1=82=20=D0=BF=D0=BE=20?= =?UTF-8?q?1=20=D0=BB=D0=B0=D0=B1=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/docs/Report 1.ipynb | 225 ++++++++++++++++++- MusinAA/docs/data/results.csv | 31 --- MusinAA/docs/data/task1/performance_plot.png | Bin 0 -> 72782 bytes MusinAA/docs/data/task1/results.csv | 31 +++ MusinAA/task1/structures/LinkedList.py | 3 + 5 files changed, 247 insertions(+), 43 deletions(-) delete mode 100644 MusinAA/docs/data/results.csv create mode 100644 MusinAA/docs/data/task1/performance_plot.png create mode 100644 MusinAA/docs/data/task1/results.csv diff --git a/MusinAA/docs/Report 1.ipynb b/MusinAA/docs/Report 1.ipynb index 3af91fe7..9ac1fa01 100644 --- a/MusinAA/docs/Report 1.ipynb +++ b/MusinAA/docs/Report 1.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 8, "id": "4689b73e", "metadata": {}, "outputs": [], @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 9, "id": "a3b5c31b", "metadata": {}, "outputs": [], @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 10, "id": "df12d41d", "metadata": {}, "outputs": [], @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 11, "id": "cc8d0436", "metadata": {}, "outputs": [], @@ -101,26 +101,227 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 12, "id": "2eedf056", "metadata": {}, "outputs": [], "source": [ "# Сохраним данные в csv\n", "import csv\n", - "with open(\"data/results.csv\", \"w\", newline=\"\") as f:\n", + "with open(\"data/task1/results.csv\", \"w\", newline=\"\") as f:\n", " writer = csv.writer(f)\n", " writer.writerows(report)" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "8523ae48", + "cell_type": "markdown", + "id": "94335af1", "metadata": {}, - "outputs": [], "source": [ - "TODO проверить работает ли оно вообще" + "# 3. Построение графиков и их анализ" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cad64d2f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAAHqCAYAAADrpwd3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/P9b71AAAACXBIWXMAAA9hAAAPYQGoP6dpAACcZElEQVR4nOzde1xVZfr///eWo2migoEUIFqjkKUF5YDiYUwMyzx+pCysPBSDpUJpovLJLCPLbOcokqUxTqXMZ0jtwCQ4o6RJNiDYNFGaoZjBGFqSppx/f/hl/9zuvREU3IKv5+OxHtO+17Xu+157kGvti3uvZaitra0VAAAAAAAAAACw0MbeEwAAAAAAAAAA4EpFER0AAAAAAAAAABsoogMAAAAAAAAAYANFdAAAAAAAAAAAbKCIDgAAAAAAAACADRTRAQAAAAAAAACwgSI6AAAAAAAAAAA2UEQHAAAAAAAAAMAGiugAAAAAAAAAANhAER1owVJSUmQwGMy2Ll26aPDgwfroo4/sPT0AANCErOX987du3brZe5oAAABAq0MRHWgF3n77bWVnZ2vXrl1avXq1HBwcNHLkSH344Yf2nhoAAGhidXn//K1///72nhoAAK3eyy+/LIPBoA8++MBiX0lJiZydnTV69OjLPzEAzcrR3hMAcOl69+6t4OBg0+u7775bnTp10vr16zVy5Eg7zgwAADS18/N+nY4dO+qHH36ww4wAALh6TJ06VQsXLtSf/vQn3XfffWb7Vq1apcrKSj355JN2mh2A5sJKdKAVcnV1lbOzs5ycnExt5eXlWrRokQICAuTq6ip3d3cNGTJEu3btkqQLfj188ODBkqQzZ87oqaeeUt++feXm5qbOnTsrJCREmzdvtpjHucc7ODjI29tbDz/8sP773/+aYg4ePCiDwaCUlBRTW2lpqW699VYFBASopKTE1L5y5UoNHDhQ1113ndq1a6dbbrlFL7/8siorK5v4HQQAoOU7c+aM4uPj5e/vL2dnZ11//fWaPn26fvnlF7O4bt266ZFHHjFr+8tf/mL19jAXup6Qzub/hQsXml6fPn1aQ4cOVdeuXfXNN9808VkCAHB5de7cWQ8++KC2bt2qgoICU3tFRYXeeOMN3XzzzRo6dKgdZwigOVBEB1qB6upqVVVVqbKyUj/88INmzZqlU6dOaeLEiZKkqqoqRURE6Pnnn9e9996rjRs3KiUlRaGhoSoqKpIks6+DL1iwQJL0/vvvm9qSkpIknf3wfPz4cT399NPatGmT1q9frwEDBmjs2LFat26dxdymTJmi7OxsZWVlafbs2UpNTdWjjz5q81xKS0v1hz/8QZWVldq2bZu8vLxM+w4cOKCJEyfqL3/5iz766CNNmTJFr7zyih5//PEmey8BAGgNamtrNXr0aC1dulRRUVH6+OOPFRcXpz//+c/6wx/+oPLycpvHlpWVac6cOXJwcDBrb8j1xPlOnz6te++9V19//bW2bdumXr16Nel5AgBgD3Urzf/0pz+Z2lJTU/Xf//7XYhX6I488YnWh2vl/wE5NTVV4eLi6du2qtm3bKiAgQHPnztWpU6eszsHWAriDBw+aYmpra5WUlKS+ffuqbdu26tSpk8aPH6/vv//erK/Bgwerd+/eFmMsXbrUos/G/PG9oqJCL7zwgnr16iUXFxd16dJFjz76qH766Ser5wRcybidC9AK/P73vzd77eLiohUrVmj48OGSpPXr12vbtm168803NXXqVFPcubd6ObePulVit912m0USdHNz09tvv216XV1draFDh+rnn3+W0WjUpEmTzOJvuOEGU98DBgzQp59+arZa7VylpaUaOnSo1QK6JC1btsz03zU1NQoLC5O7u7seffRRvfrqq+rUqZP1NwgAgKtMRkaGtmzZopdfflmzZ8+WJA0bNkw+Pj6KjIzUunXrNG3aNKvHPvvss3JwcNDo0aOVk5Njam/I9cS5Tp8+rZEjR1JABwC0OrfeeqsGDhyodevWKTExUW5ubvrTn/6kTp06KSoqyiK+bdu2+uc//2l6/Yc//MEiZv/+/RoxYoRmzZqldu3a6ZtvvtGSJUv0xRdfmB17rilTpphy8scff6wXXnjBbP/jjz+ulJQUzZgxQ0uWLNHx48e1aNEihYaGau/evfL09LyUt0GS7T++19TUaNSoUdqxY4fmzJmj0NBQHTp0SM8++6wGDx6snJwctW3b9pLHBy4XiuhAK7Bu3ToFBARIOluI3rhxo6ZPn67q6mo98cQT+vvf/y5XV1dNnjy5Scb7v//7PxmNRu3du9fsr+Kurq4WsTU1NaqqqlJ1dbW++OIL7dy5U8OGDbOIO3bsmIYOHaovv/xS//nPfywK6JKUl5enZ599Vp999pmOHz9utm/fvn3q169fE5wdAAAtX92H7fNXiv3P//yPJk+erH/84x9Wi+hfffWVVqxYoXfeeUd///vfzfY15nri9OnTuu+++/SPf/xDH3/8MQV0AECr8+STT+p//ud/9Pbbb6tfv37617/+paefflrXXHONWVx5ebmcnJzMFq61aWN5Y4i6b4RLZ1eQ9+/fXwEBARo0aJC+/PJL3Xrrrab9FRUVks6uCq/r9/xbpn3++ed688039eqrryouLs7UHhYWpt/97ndatmyZlixZcgnvwFm2/vj+17/+VZ988onS0tI0duxYU3ufPn10xx13KCUlRX/84x8veXzgcuF2LkArEBAQoODgYAUHB+vuu+/WG2+8ofDwcM2ZM0e//PKLfvrpJ3l7e1tN1I31/vvva8KECbr++uv1zjvvKDs7W//61780efJknTlzxiL++eefl5OTk1xdXTVw4EDdeOONMhqNFnHz5s1TRUWFvLy8lJCQYLG/qKhIYWFhOnLkiF5//XXt2LFD//rXv7Ry5UpJZz+sAwCAs44dOyZHR0d16dLFrN1gMMjLy0vHjh2zetz06dMVFhamyMhIi32NuZ4wGo366quv1KtXLy1atEhVVVUXdyIAAFyhRo8eLR8fH61YsUJGo1EODg6aPn26RdzJkyctCuvWfP/995o4caK8vLzk4OAgJycnDRo0SJLM7r0u/f+ff60tZKvz0UcfyWAw6KGHHlJVVZVp8/LyUp8+fbR9+3aLY86Nq6qqUk1NTb1zrvvj+6uvvqr27dtbjN+xY0eNHDnSrM++ffvKy8vL6vjAlYyV6EArdeutt2rLli3at2+funTpop07d6qmpuaSC+nvvPOO/P39lZqaKoPBYGq3dW/VadOm6bHHHlNtba1+/PFHvfjiiwoJCVF+fr6uvfZaU1z37t21bds27d27VxEREVqzZo2mTJli2r9p0yadOnVK77//vvz8/Ezt+fn5l3Q+AAC0Ru7u7qqqqtJPP/1kVkivra1VSUmJ7rjjDotj3n33XWVnZ9vMrY25nujcubO2bdumiooK3XnnnXruuef0/PPPX9I5AQBwJXF0dNQf//hHzZs3TwcOHNDo0aMtbocqSUeOHJG3t3e9fZ08eVJhYWFydXXVCy+8oN/97ne65pprdPjwYY0dO9Zi0VhpaakkycPDw2af//3vf1VbW2vzli3du3c3e/2f//xHTk5O9c7zfOf+8f38b7D997//1S+//CJnZ2erx9adA9BSUEQHWqm6D8BdunRRRESE1q9fr5SUlEu+pYvBYJCzs7NZAb2kpESbN2+2Gu/t7a3g4GDT69raWo0ZM0bZ2dkKDw83tT/zzDPy8vKSl5eXnnzySc2cOdP0NbO6caWz93s/t68333zzks4HAIDWaOjQoXr55Zf1zjvvKDY21tSelpamU6dOaejQoWbxv/76q2bPnq2ZM2cqMDDQap+NuZ54/PHHTbdwSUxM1NNPP63w8HCFhYVd4pkBAHDlmDZtmhYtWqQzZ85oxowZFvsrKytVUFBg9Rte5/rnP/+pH3/8Udu3bzetPpekX375xWr8/v37JUk33nijzT49PDxkMBi0Y8cOs8/Rdc5v69GjhzZs2GDW9s477+j111+32v+F/vju4eEhd3d3ffLJJ1b3n7uoDmgJKKIDrcBXX31l+pr0sWPH9P777yszM1NjxoyRv7+/fHx89Pbbbys6OlrffvuthgwZopqaGu3evVsBAQG6//77GzzWvffeq/fff18xMTEaP368Dh8+rOeff15du3Y1JfJz/fDDD/r8889NK9ETExPl4uJiuoe7NUuWLNE///lPPfjgg9q1a5ecnJw0bNgwOTs764EHHtCcOXN05swZrVq1Sj///HPj3zAAAFq5YcOGafjw4XrmmWdUVlam/v3768svv9Szzz6r2267zeKhZ5s3b5anp6eeffZZm30+8MADF3U9MWvWLP3973/XQw89pL1796pjx45NeaoAANhNhw4d1L59e910000aMmSIxf6MjAydOXPG5kO461hbNCZJb7zxhtX4TZs2qV27dgoKCrLZ57333quXXnpJR44c0YQJEy50KnJ1dTVbACfJ5i1XGvLH93vvvVcbNmxQdXU1zy9Dq0ARHWgFHn30UdN/u7m5yd/fX8uWLVNMTIyks18zS09PV2JiotavXy+j0ahrr71Wffr00d13393osY4ePark5GStXbtW3bt319y5c/XDDz/oueees4hfs2aN1qxZI4PBoM6dO6tPnz76+9//Lh8fH5tjuLq66t1339Wdd96phIQEvfTSS+rVq5fS0tK0YMECjR07Vu7u7po4caLi4uIUERHRqHMAAKC1MxgM2rRpkxYuXKi3335bixcvloeHh6KiovTiiy9afEivrq62ej/Tc13s9YTBYFBK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1Pb222/rf/7nf0z/4F1dXXX69Gmb/ffp00ePP/64brnlFt1000164YUX1L17d33wwQdW4//85z/rzJkzGjVqVENP95Lt27dPa9euVWxs7GUbEwAAAJcP18eNw/UxAOBKRU5vHHL6xaGIfpWora2VdPZrJxdSVlZm9S93daZOnaq3335bknT06FF9/PHHmjx5smn/zTffrL/97W+qrKy0evypU6c0Z84cBQYGqmPHjmrfvr2++eYbq3+V++2337RgwQK98sorcnS0vIV/UlKS2rdvb9pefPFFi5iPPvpI7du3V8eOHXXLLbdo5cqVF3wP5syZo8cff1zdu3e/YCwAAABaHq6PuT4GALQO5HRy+uVAEf0qcdNNN8lgMKigoOCCsT/++KO8vb1t7p80aZK+//57ZWdn65133lG3bt0UFhZm2v/aa6/p008/Vbt27dS+fXtFR0ebHT979mylpaVp8eLF2rFjh/Lz83XLLbeooqLCYqxXXnlFPXv21MiRI63O5cEHH1R+fr5pO38sSRoyZIjy8/P1+eefKzo6WjNmzNA//vEPm+eXlZWlHTt2aMGCBTZjAAAA0LJxfcz1MQCgdSCnk9MvB8s/c6BV6ty5s4YPH66VK1dqxowZFn91++WXX9SxY0edOnVKBQUFio+Pt9mXu7u7Ro8erbffflvZ2dl69NFHzfaHhYWppKRERUVFqq6u1vvvv2/217IdO3bokUce0ZgxYySdvV/UwYMHLcYpLi7WqlWrtH37dptzcXNz04033mh2nudr166dKaZXr1567bXXlJeXZ/WvfLW1tXrqqaeUkJCgTp062RwX51noZu8ZXJqFJ+w9AwAArgwXk9Pb+0j9X5WOnpYcL7wCrNn8mCd539bgcK6PuT4GgFatsTn9SsnnEjn9/yGnX1nsvhI9KSlJ/v7+cnV1VVBQkHbs2GEztri4WBMnTlTPnj3Vpk0bi6fU1vnll180ffp0de3aVa6urgoICFB6enoznUHLkZSUpOrqat15551KS0vT/v37VVBQoOXLlyskJETffPONHnjgAXXs2FERERH19jV16lT9+c9/VkFBgR5++GGL/Q4ODvL399eNN96o6667zmzfjTfeqPfff1/5+fnau3evJk6cqJqaGos+Vq5cqTFjxuj222+/pPOuqanRmTNndPLkSX3wwQc6dOiQbrnlFqux//jHP3TixAnFxMRc0pgAAAC48nF9zPUxAKB1IKeT05ubXVeip6amatasWUpKSlL//v31xhtvKCIiQl9//bV8fX0t4svLy9WlSxfNnz9fr732mtU+KyoqNGzYMF133XX629/+phtuuEGHDx/Wtdde29ync8Xz9/fXnj17tHjxYj311FMqLi5Wly5dFBQUpFWrVmnhwoWqqqrS1q1bL/hU4Lvuuktdu3bVzTffXO/XYKx57bXXNHnyZIWGhsrDw0PPPPOMysrKLOJqamq0ePHiRvVtzYcffqi2bdvK0dFRvr6+SkxM1PDhw61+zefUqVN66aWXzB4sAQAAgNaJ62OujwEArQM5nZze3Ay1dXfft4N+/frp9ttv16pVq0xtAQEBGj16tBITE+s9dvDgwerbt6+MRqNZe3Jysl555RV98803cnJyuqh5lZWVyc3NTSdOnFCHDh3M9p05c0aFhYWm1fNXq99++03e3t5au3atxo4da+/poIlc9M83t3MBGqS+/ILmwXsONNJF5PQz7X1U2P9V+V/fRa72/vp3I7763dS4Pm596rs2Jr9cfrznQCM1MqdfUflcIqejSTVFTrfb7VwqKiqUm5ur8PBws/bw8HDt2rXrovv94IMPFBISounTp8vT01O9e/fWiy++qOrq6kudMnT2L2U//vijEhIS5Obmpvvuu8/eUwIAAADshutjAABaB3I66mO327mUlpaqurpanp6eZu2enp4qKSm56H6///57/fOf/9SDDz6o9PR07d+/X9OnT1dVVZX+93//1+ox5eXlKi8vN7229jULnFVUVCR/f3/dcMMNSklJsfqgAgAAAOBqwfUxAACtAzkd9bH7T4PBYP4VkdraWou2xqipqdF1112n1atXy8HBQUFBQfrxxx/1yiuv2CyiJyYm6rnnnrvoMa8m3bp1kx3vAAQAAABcUbg+BgCgdSCnoz52u52Lh4eHHBwcLFadHz161GJ1emN07dpVv/vd7+Tg4GBqCwgIUElJiSoqKqweEx8frxMnTpi2w4cPX/T4AAAAAAAAAIDWw25FdGdnZwUFBSkzM9OsPTMzU6GhoRfdb//+/fXdd9+ppqbG1LZv3z517drV5tNnXVxc1KFDB7MNAAAAAAAAAAC7FdElKS4uTm+99ZbWrl2rgoICxcbGqqioSNHR0ZLOrhCfNGmS2TH5+fnKz8/XyZMn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lzWtS5cuOnv2rH744QcdPHhQeXl5uv/+++2W88477+jbb78td3udPn1aDRo0cDl/9+7d2rJli91y27RpI0l2y77rrrvs2hcbG6sjR46oqKhI6enpMsaoVatWdsvZunWr3TJKtoO/v7/uuusujRw50tp2GRkZuvXWW+3OVHfp0kXFxcX6+uuvnbbd/P9HCZRuV3lKvu+GDRvqzJkzdr8giI6OVkpKik6cOOHy89u2bVNAQICCg4N14cIFTZ8+3Zq3ZcsW3X///WratKkCAwOVkJCg06dP69y5cw7r76oflGjWrJldOWc6d+6sgIAANWvWTD169FBmZma5617RvlDi5MmT+vHHH3XvvfeWW85d58+f1/jx4/X666+rbt261vSMjAzl5+e7rMfd/pCZmanu3bsrLy9P3bp1q5I2V8aiRYtUr149ZWZm6ocffrCbd9NNN1nfYa9evZSfn293XKiMyZMn2/WJsscgV9zpl126dJG3979j6He/+50uXryob775xpqWm5vr9FckZRlj9MILL2jSpEkKCgqqxBr+27x589SpUyddf/31CggI0IIFC9xeX9Q+ZDqZXoJMJ9PJ9PKR6ajpyHQyvQSZTqaT6eUj0y8Pg+iVUL9+fd1444268cYbdccdd2jRokXy8/PTBx984FD2hRde0IgRIxQWFuZ0WTNmzLCCfu/everUqZM179y5c4qPj1dAQID+53/+R2lpaVq9erWkS5ePVaR0YAQGBtrVs27dOruyxhiHgCkdPMXFxZKktWvX2i3n4MGD+sc//lFuO7777jtFRUW5nF9cXKyHH37Ybrl79+7VkSNHdPfdd1e4niXLqFOnjnbv3m23jIyMDL311lsO22H//v366KOPtGTJEi1ZssTlNijhanrLli3l5eXl9gGv5PvetWuXoqOj9cc//tGaN3HiREVFRVnB6Cw4O3XqpD179mjDhg06ffq0FixYIEn6/vvv9dBDD6l9+/ZKTk7W7t27NXv2bEmyu1ywon5QYtu2bXblnFmxYoX27t2r999/X1lZWUpISCh33SvaF0r8lsspy/P666+rdevWevjhhytVj7v9Yf/+/Ro4cKAef/xxPfnkk9a+ciWkpqZqxowZ+vDDDxUbG6uBAwda+60krVu3zvoOS45Pv3X7Dho0yK5PhIeHV/gZd/plw4YN3drOP/74o1t1vvPOOzp3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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "df = pd.read_csv('data/task1/results.csv')\n", + "mean_times = df.groupby(['Структура', 'Режим'])[['Вставка', 'Поиск', 'Удаление']].mean().reset_index()\n", + "structures = mean_times['Структура'].unique()\n", + "modes = mean_times['Режим'].unique()\n", + "\n", + "fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n", + "operations = ['Вставка', 'Поиск', 'Удаление']\n", + "\n", + "for ax, op in zip(axes, operations):\n", + " # a\n", + " x = np.arange(len(structures))\n", + " width = 0.35\n", + " \n", + " random_vals = []\n", + " sorted_vals = []\n", + " for s in structures:\n", + " random_row = mean_times[(mean_times['Структура']==s) & (mean_times['Режим']=='Cлучайный')]\n", + " sorted_row = mean_times[(mean_times['Структура']==s) & (mean_times['Режим']=='Отсортированный')]\n", + " random_vals.append(random_row[op].values[0] if not random_row.empty else 0)\n", + " sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0)\n", + " \n", + " ax.bar(x - width/2, random_vals, width, label='Случайный')\n", + " ax.bar(x + width/2, sorted_vals, width, label='Отсортированный')\n", + " ax.set_xticks(x)\n", + " ax.set_xticklabels(structures)\n", + " ax.set_ylabel('Время (сек)')\n", + " ax.set_title(op)\n", + " ax.legend()\n", + "\n", + "plt.tight_layout()\n", + "plt.savefig('data/task1/performance_plot.png', dpi=150)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "1d86131d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
СтруктураРежимВставкаПоискУдаление
0Бинарное деревоCлучайный0.0119210.0001500.000137
1Бинарное деревоОтсортированный0.1221710.0016270.000873
2Связанный списокCлучайный0.0900390.0008930.000605
3Связанный списокОтсортированный0.1624470.0017070.000915
4Хэш-таблицаCлучайный0.0448310.0006180.000324
5Хэш-таблицаОтсортированный0.0493690.0005270.000272
\n", + "
" + ], + "text/plain": [ + " Структура Режим Вставка Поиск Удаление\n", + "0 Бинарное дерево Cлучайный 0.011921 0.000150 0.000137\n", + "1 Бинарное дерево Отсортированный 0.122171 0.001627 0.000873\n", + "2 Связанный список Cлучайный 0.090039 0.000893 0.000605\n", + "3 Связанный список Отсортированный 0.162447 0.001707 0.000915\n", + "4 Хэш-таблица Cлучайный 0.044831 0.000618 0.000324\n", + "5 Хэш-таблица Отсортированный 0.049369 0.000527 0.000272" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.read_csv('data/task1/results.csv')\n", + "df.groupby(['Структура', 'Режим'])[['Вставка', 'Поиск', 'Удаление']].mean().reset_index()" + ] + }, + { + "cell_type": "markdown", + "id": "c9a486a5", + "metadata": {}, + "source": [ + "# 4. Анализ результатов\n", + "---\n", + "### 4.1 Влияние порядка данных на вставку в BST\n", + "При вставке элементов в отсортированном порядке в бинарное дерево оно превращается в связный список - это связанно с тем, что все элементы вставляются в одну ветвь дерева. Сложность всех операций приблтижается к **O(n)**. Вставка в BST на отсортированных данных заняла 0.122171c вместо 0.011921с, разница более чем в 10 раз. Причём, время вставки даже хуже, чем у чистого связнного списка - это связанно с дополнительными расходами бинарного дерева. Поиск так же ухудшился, примерно в 10 раз, а с ним ухудшилось и удаление.\n", + "\n", + "### 4.2 Почему хэш-таблица почти не чувствительна к порядку\n", + "По графикам видно, что для хэш-таблицы время операций почти не изменяется. Исключение составляет лишь поиск, его время больше на отсортированных данных. Это связано с особенностями теста - поиск 10 несуществующих записей ухудшают результат для отсортированных данных. Все эти наблюдения связаны с механизмом работы хэш-таблицы - она распределяет данные по корзинам независимо от порядка поступления. Получается, что сложность всех операций **O(1)**\n", + "\n", + "### 4.3 Почему связный список всегда медленен при поиске\n", + "Для поиска в связном списке нужно просматривать все элементы по порядку, так что сложность всех операций **O(n)**\n", + "\n", + "### 4.4 Сравнение удаления\n", + "\n", + "- **Связаный список** удаление требует сначала найти элемент за O(n), затем переставить ссылки за O(1). Время удаления (0.000605 с) близко ко времени поиска, что логично.\n", + "- **Хеш-таблица:** при удалении, поиск корзины за O(1) и поиск в коротком связаном списке за O(n) удаляется элемент. Время удаления (0.000324) меньше, чем в списке.\n", + "- **BST:** на случайных данных удаление очень быстрое (0.000137 с) благодаря логарифмической высоте. На отсортированных данных время возрастает до 0.000873, что отражает деградацию до O(n)." + ] + }, + { + "cell_type": "markdown", + "id": "a7ed5470", + "metadata": {}, + "source": [ + "# 5. Вывод\n", + "На основе полученных результатов можно сформулировать следующие рекомендации:\n", + "\n", + "- Хеш-таблица – хороший выбор, если приоритетом является максимальная скорость вставки, поиска и удаления по ключу, а порядок элементов не имеет значения. Время операций близко к **O(1)** и практически не зависит от упорядоченности входных данных. Идеальна для кэшей, словарей и частых запросов по идентификатору.\n", + "\n", + "- Двоичное дерево поиска – следует применять, когда необходимо получать данные в отсортированном порядке. На случайных данных демонстрирует хорошую производительность **O(log n)**, однако при поступлении заранее отсортированных элементов вырождается в связный список с падением скорости до **O(n)**.\n", + "\n", + "- Связный список – демонстрирует линейную сложность поиска и удаления **O(n)** что делает его непригодным для задач с частым доступом к произвольным элементам. Может быть оправдан только в узких случаях, где вставки и удаления происходят исключительно в начале или конце коллекции (очереди, стеки) и не требуется поиск.\n", + "\n", + "Таким образом, для реальных задач чаще всего выбирают хеш-таблицы или сбалансированные деревья в зависимости от требований к упорядоченности данных.\n" ] } ], @@ -140,7 +341,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.3" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/MusinAA/docs/data/results.csv b/MusinAA/docs/data/results.csv deleted file mode 100644 index 8f5e275b..00000000 --- a/MusinAA/docs/data/results.csv +++ /dev/null @@ -1,31 +0,0 @@ -Структура,Режим,Вставка,Поиск,Удаление -Связанный список,Cлучайный,4.661078881000321,0.012941928999680385,0.020117076999667916 -Хэш-таблица,Cлучайный,0.18879180299973086,0.0014481220005109208,0.0012560499999381136 -Бинарное дерево,Cлучайный,0.01616830700004357,0.00016560199946979992,0.00016564300040045055 -Связанный список,Отсортированный,5.038275819999399,0.07141196100019442,0.1353478549999636 -Хэш-таблица,Отсортированный,0.1631836659998953,0.003129734999674838,0.003202291999514273 -Бинарное дерево,Отсортированный,0.16634428900033527,0.0017354540004816954,0.001874036000117485 -Связанный список,Cлучайный,4.664316974999565,0.014653709000413073,0.02280332600003021 -Хэш-таблица,Cлучайный,0.186777711999639,0.0010283499996148748,0.0009878339997158037 -Бинарное дерево,Cлучайный,0.016256722999969497,0.00015175599946815055,0.0001649409996389295 -Связанный список,Отсортированный,5.907060995999927,0.08458327099924645,0.12409427100010362 -Хэш-таблица,Отсортированный,0.18781050100005814,0.003518858999996155,0.0038270310005827923 -Бинарное дерево,Отсортированный,0.17500397699950554,0.0019018089997189236,0.001833940000324219 -Связанный список,Cлучайный,5.755159846999959,0.016742109000006167,0.026386416000605095 -Хэш-таблица,Cлучайный,0.22951744300007704,0.0009921219998432207,0.0011742059996322496 -Бинарное дерево,Cлучайный,0.01580532200023299,0.00017768500038073398,0.00019722199976968113 -Связанный список,Отсортированный,6.30150953500015,0.11176149099992472,0.1474957850005012 -Хэш-таблица,Отсортированный,0.18557919999966543,0.003155624000100943,0.002713390000280924 -Бинарное дерево,Отсортированный,0.19082545899982506,0.001985636999961571,0.002389950000178942 -Связанный список,Cлучайный,6.395210606000546,0.014804241999627266,0.02304338900012226 -Хэш-таблица,Cлучайный,0.1902105980007036,0.000852088000101503,0.0009626670007492066 -Бинарное дерево,Cлучайный,0.01663886400001502,0.00016400000004068715,0.000184548000106588 -Связанный список,Отсортированный,4.850914527999521,0.0771323629996914,0.1152741280002374 -Хэш-таблица,Отсортированный,0.17607759100064868,0.002924628000073426,0.0033850670006358996 -Бинарное дерево,Отсортированный,0.19345043999965128,0.0018585970001367969,0.0019752460002564476 -Связанный список,Cлучайный,4.803303787999539,0.015972447000422108,0.0223228390004806 -Хэш-таблица,Cлучайный,0.19020581800032232,0.0011616620004133438,0.0009839170006671338 -Бинарное дерево,Cлучайный,0.016469425000650517,0.000160212000082538,0.00017693399968266021 -Связанный список,Отсортированный,4.741838529000233,0.075463203000254,0.10462550600004761 -Хэш-таблица,Отсортированный,0.16722737300005974,0.004002030999799899,0.005207103999964602 -Бинарное дерево,Отсортированный,0.16575109800032806,0.0020921670002280734,0.002277146999404067 diff --git a/MusinAA/docs/data/task1/performance_plot.png b/MusinAA/docs/data/task1/performance_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..f4080211dd4ee427dcfee91e89880fee38ddc44a GIT binary patch literal 72782 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 TJ6_yAU$a dict: currentNode = head while currentNode['next'] != None: + if currentNode['name'] == name: + currentNode['phone'] = phone + return head currentNode = currentNode['next'] currentNode['next'] = newNode return head From b002e859589720fda848a4e37fcced96f98e83f1 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Fri, 24 Apr 2026 06:14:07 +0300 Subject: [PATCH 13/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=8D=D1=82=D0=B0=D0=BF=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/__init__.py | 0 MusinAA/task2/mazeObjects/__init__.py | 0 MusinAA/task2/mazeObjects/cell.py | 13 +++++++++ MusinAA/task2/mazeObjects/maze.py | 40 +++++++++++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 MusinAA/task2/__init__.py create mode 100644 MusinAA/task2/mazeObjects/__init__.py create mode 100644 MusinAA/task2/mazeObjects/cell.py create mode 100644 MusinAA/task2/mazeObjects/maze.py diff --git a/MusinAA/task2/__init__.py b/MusinAA/task2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MusinAA/task2/mazeObjects/__init__.py b/MusinAA/task2/mazeObjects/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MusinAA/task2/mazeObjects/cell.py b/MusinAA/task2/mazeObjects/cell.py new file mode 100644 index 00000000..2eaf73fd --- /dev/null +++ b/MusinAA/task2/mazeObjects/cell.py @@ -0,0 +1,13 @@ +class Cell: + """Хранит координаты (x, y) + флаги isWall, isStart, isExit + метод isPassable() (возвращает True для прохода, если не стена).""" + def __init__(self, x: int, y: int, isWall = False, isStart = False, isExit = False): + self.x = x + self.y = y + self.isWall = isWall + self.isStart = isStart + self.isExit = isExit + + def isPassable(self): + return not self.isWall diff --git a/MusinAA/task2/mazeObjects/maze.py b/MusinAA/task2/mazeObjects/maze.py new file mode 100644 index 00000000..042b8df1 --- /dev/null +++ b/MusinAA/task2/mazeObjects/maze.py @@ -0,0 +1,40 @@ +from task2.mazeObjects.cell import Cell + +class Maze: + """Хранит двумерный массив клеток, + ширину, высоту, ссылки на стартовую и выходную клетку. + Методы: + getCell(x, y), getNeighbors(cell) – возвращает список соседних проходимых клеток + (вверх, вниз, влево, вправо, если в пределах границ и не стена).""" + + def __init__(self, mazeArray: list[list[Cell]], start: dict, end: dict) -> None: + self.mazeArray = mazeArray + self.width = len(mazeArray) + self.height = len(mazeArray[0]) + + self.startCell = self.getCell(start['x'], start['y']) + self.endCell = self.getCell(end['x'], end['y']) + + def getCell(self, x: int, y: int): + return self.mazeArray[y][x] + + def checkCell(self, x: int, y: int): + if not(0 <= x and x < self.width): + return False + if not(0 <= y and y < self.height): + return False + return self.getCell(x, y).isPassable() + + def getNeighbors(self, cell: Cell): + point = (cell.x, cell.y) + offsets = ((0, 1), + (0, -1), + (-1, 0), + (1, 0)) + passableCells = [] + for ofst in offsets: + x = point[0]+ofst[0] + y = point[1]+ofst[1] + if self.checkCell(x, y): + passableCells.append(self.getCell(x, y)) + return passableCells \ No newline at end of file From c7c181f30b7c5611e88d806f6117aa1fa66bfb3f Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Tue, 28 Apr 2026 01:11:11 +0300 Subject: [PATCH 14/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=8D=D1=82=D0=B0=D0=BF=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/mazeBuilder.py | 72 +++++++++++++++++++++++++++++++ MusinAA/task2/mazeObjects/cell.py | 2 +- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 MusinAA/task2/mazeBuilder.py diff --git a/MusinAA/task2/mazeBuilder.py b/MusinAA/task2/mazeBuilder.py new file mode 100644 index 00000000..7ddcc0f2 --- /dev/null +++ b/MusinAA/task2/mazeBuilder.py @@ -0,0 +1,72 @@ +from abc import ABC, abstractmethod +from itertools import product +import sys + +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.cell import Cell + +class MazeBuilder(ABC): + """Интерфейс MazeBuilder с методом buildFromFile(filename)""" + + @abstractmethod + def buildFromFile(self, filename: str): + """Создание лабиринта из файла.""" + + +class TextFileMazeBuilder(MazeBuilder): + """Читает файл, парсит символы, + создаёт объекты Cell, + задаёт координаты и флаги, + после чего возвращает готовый Maze.""" + + start = {'x': 0, 'y': 0} + end = {'x': 0, 'y': 0} + + def cellStrategy(self, letter: str) -> Cell: + if letter == '#': + return Cell(isWall=True) + elif letter == ' ': + return Cell() + elif letter == 'S': + return Cell(isStart=True) + elif letter == 'E': + return Cell(isExit=True) + else: + sys.stderr.write(f"Неизвестный символ '{letter}' при загрузке из файла\n") + return Cell() + + def updateStartEnd(self, letter: str, x:int, y:int) -> None: + if letter == 'S': + self.start = {'x': x, 'y': y} + elif letter == 'E': + self.end = {'x': x, 'y': y} + + def generate_row_from_txt(self, filename: str) -> list[str]: + with open(filename) as file: + text = file.read() + text = text.strip() + if not text: + raise ValueError(f"Файл \"{filename}\" пуст") + text = text.split('\n') + return text + + def buildFromFile(self, filename: str): + rows = self.generate_row_from_txt(filename) + height = len(rows) + width = len(rows[0]) + array = [[Cell() for j in range(width)] for i in range(height)] + + # Здесь x и y где-то перепутаны, но мне лень это чинить + try: + for x, y in product(range(width), range(height)): + cell = self.cellStrategy(rows[y][x]) + self.updateStartEnd(rows[y][x], x, y) + cell.x = x + cell.y = y + array[y][x] = cell + except IndexError: + raise ValueError(f"Строка {x+1} имеет длину {len(rows[x])}, ожидалось {width}") + + return Maze(array, self.start, self.end) + + \ No newline at end of file diff --git a/MusinAA/task2/mazeObjects/cell.py b/MusinAA/task2/mazeObjects/cell.py index 2eaf73fd..9d617aba 100644 --- a/MusinAA/task2/mazeObjects/cell.py +++ b/MusinAA/task2/mazeObjects/cell.py @@ -2,7 +2,7 @@ class Cell: """Хранит координаты (x, y) флаги isWall, isStart, isExit метод isPassable() (возвращает True для прохода, если не стена).""" - def __init__(self, x: int, y: int, isWall = False, isStart = False, isExit = False): + def __init__(self, x: int = 0, y: int = 0, isWall:bool = False, isStart:bool = False, isExit:bool = False): self.x = x self.y = y self.isWall = isWall From f3978396eb2dcc6c3e49bba5667f903868ab21df Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sat, 16 May 2026 19:19:51 +0300 Subject: [PATCH 15/24] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20width=20<=3D>?= =?UTF-8?q?=20height=20=D0=B2=20maze.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/mazeObjects/maze.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MusinAA/task2/mazeObjects/maze.py b/MusinAA/task2/mazeObjects/maze.py index 042b8df1..a9aae9dc 100644 --- a/MusinAA/task2/mazeObjects/maze.py +++ b/MusinAA/task2/mazeObjects/maze.py @@ -9,8 +9,8 @@ class Maze: def __init__(self, mazeArray: list[list[Cell]], start: dict, end: dict) -> None: self.mazeArray = mazeArray - self.width = len(mazeArray) - self.height = len(mazeArray[0]) + self.height = len(mazeArray) + self.width = len(mazeArray[0]) self.startCell = self.getCell(start['x'], start['y']) self.endCell = self.getCell(end['x'], end['y']) From 89c11085ba168e05907d5f207b9d98b973a18a72 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sat, 16 May 2026 19:20:59 +0300 Subject: [PATCH 16/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20BFS,=20=D1=81=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D1=8B=20=D1=88=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=B0=D0=BB?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/strategyObjects/AStar.py | 9 ++++ MusinAA/task2/strategyObjects/BFS.py | 42 +++++++++++++++++++ MusinAA/task2/strategyObjects/DFS.py | 9 ++++ MusinAA/task2/strategyObjects/__init__.py | 0 .../strategyObjects/pathFindingStrategy.py | 12 ++++++ 5 files changed, 72 insertions(+) create mode 100644 MusinAA/task2/strategyObjects/AStar.py create mode 100644 MusinAA/task2/strategyObjects/BFS.py create mode 100644 MusinAA/task2/strategyObjects/DFS.py create mode 100644 MusinAA/task2/strategyObjects/__init__.py create mode 100644 MusinAA/task2/strategyObjects/pathFindingStrategy.py diff --git a/MusinAA/task2/strategyObjects/AStar.py b/MusinAA/task2/strategyObjects/AStar.py new file mode 100644 index 00000000..7b1b45e6 --- /dev/null +++ b/MusinAA/task2/strategyObjects/AStar.py @@ -0,0 +1,9 @@ +from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy + +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.cell import Cell + +class AStar(PathFindingStrategy): + """Алгоритм с эвристикой (etc. манхэттенское расстояние) – компромисс между скоростью и оптимальностью.""" + def findPath(self, maze: Maze, start: Cell, exit: Cell): + ... \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/BFS.py b/MusinAA/task2/strategyObjects/BFS.py new file mode 100644 index 00000000..a76f3a51 --- /dev/null +++ b/MusinAA/task2/strategyObjects/BFS.py @@ -0,0 +1,42 @@ +from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy + +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.cell import Cell + +import queue + +class BFS(PathFindingStrategy): + """Поиск в ширину – гарантирует кратчайший путь по количеству шагов.""" + def findPath(self, maze: Maze, start: Cell, exit: Cell): + visited = dict() + parents = dict() + q = queue.Queue() + + q.put(start) + visited[start] = 0 + parents[start] = None + + while not q.empty(): + current = q.get() + + # Условие нахождение выхода + if current == exit: break + + # Перебор соседей + for hood in maze.getNeighbors(current): + if hood in visited: + continue + visited[hood] = visited[current] + 1 + parents[hood] = current + q.put(hood) + return self.restorePath(parents, start, exit) + + def restorePath(self, parents: dict, start: Cell, exit: Cell) -> list[Cell]|None: + path = [] + current = exit + while current: + path.append(current) + if current not in parents: + return [] + current = parents[current] + return path[::-1] \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/DFS.py b/MusinAA/task2/strategyObjects/DFS.py new file mode 100644 index 00000000..707be203 --- /dev/null +++ b/MusinAA/task2/strategyObjects/DFS.py @@ -0,0 +1,9 @@ +from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy + +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.cell import Cell + +class DFS(PathFindingStrategy): + """Поиск в глубину – быстрый, но не обязательно кратчайший.""" + def findPath(self, maze: Maze, start: Cell, exit: Cell): + ... \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/__init__.py b/MusinAA/task2/strategyObjects/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MusinAA/task2/strategyObjects/pathFindingStrategy.py b/MusinAA/task2/strategyObjects/pathFindingStrategy.py new file mode 100644 index 00000000..0a1cb92e --- /dev/null +++ b/MusinAA/task2/strategyObjects/pathFindingStrategy.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod + +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.cell import Cell + +class PathFindingStrategy(ABC): + """Интерфейс PathFindingStrategy с методом findPath(maze, start, exit), + возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет.""" + + @abstractmethod + def findPath(self, maze: Maze, start: Cell, exit: Cell): + """Возвращает список клеток пути от старта до выхода включительно. Пути нет - пустой список.""" \ No newline at end of file From 1a562a75941aaf8190f0e428d7530bd1978afc58 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sun, 17 May 2026 00:45:29 +0300 Subject: [PATCH 17/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20DFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/strategyObjects/BFS.py | 5 ++-- MusinAA/task2/strategyObjects/DFS.py | 38 ++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/MusinAA/task2/strategyObjects/BFS.py b/MusinAA/task2/strategyObjects/BFS.py index a76f3a51..becac590 100644 --- a/MusinAA/task2/strategyObjects/BFS.py +++ b/MusinAA/task2/strategyObjects/BFS.py @@ -6,7 +6,8 @@ from task2.mazeObjects.cell import Cell import queue class BFS(PathFindingStrategy): - """Поиск в ширину – гарантирует кратчайший путь по количеству шагов.""" + """Поиск в ширину – гарантирует кратчайший путь по количеству шагов. + Возвращает None, если пути нет""" def findPath(self, maze: Maze, start: Cell, exit: Cell): visited = dict() parents = dict() @@ -37,6 +38,6 @@ class BFS(PathFindingStrategy): while current: path.append(current) if current not in parents: - return [] + return None current = parents[current] return path[::-1] \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/DFS.py b/MusinAA/task2/strategyObjects/DFS.py index 707be203..88f581c0 100644 --- a/MusinAA/task2/strategyObjects/DFS.py +++ b/MusinAA/task2/strategyObjects/DFS.py @@ -4,6 +4,40 @@ from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell class DFS(PathFindingStrategy): - """Поиск в глубину – быстрый, но не обязательно кратчайший.""" + """Поиск в глубину – быстрый, но не обязательно кратчайший. + Возвращает None, если пути нет""" def findPath(self, maze: Maze, start: Cell, exit: Cell): - ... \ No newline at end of file + visited = dict() + parents = dict() + stack = [] + + stack.append(start) + visited[start] = 0 + parents[start] = None + + while stack: + current = stack.pop() + + # Условие нахождение выхода + if current == exit: break + + # Перебор соседей + for hood in maze.getNeighbors(current): + if hood in visited: + continue + visited[hood] = visited[current] + 1 + parents[hood] = current + stack.append(hood) + + return self.restorePath(parents, start, exit) + + def restorePath(self, parents: dict, start: Cell, exit: Cell) -> list[Cell]|None: + path = [] + current = exit + while current: + path.append(current) + if current not in parents: + return None + current = parents[current] + return path[::-1] + \ No newline at end of file From d5f28df86ac352dd4cdd984c87a3cd1b19d0d1da Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sun, 17 May 2026 00:48:23 +0300 Subject: [PATCH 18/24] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3:=20=D0=92=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=20restorePath()=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BE=D0=B1=D1=89=D0=B8=D0=B9?= =?UTF-8?q?=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/strategyObjects/BFS.py | 13 ++----------- MusinAA/task2/strategyObjects/DFS.py | 15 +++------------ MusinAA/task2/strategyObjects/util.py | 12 ++++++++++++ 3 files changed, 17 insertions(+), 23 deletions(-) create mode 100644 MusinAA/task2/strategyObjects/util.py diff --git a/MusinAA/task2/strategyObjects/BFS.py b/MusinAA/task2/strategyObjects/BFS.py index becac590..d533fb85 100644 --- a/MusinAA/task2/strategyObjects/BFS.py +++ b/MusinAA/task2/strategyObjects/BFS.py @@ -1,4 +1,5 @@ from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy +from task2.strategyObjects.util import restorePath from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell @@ -30,14 +31,4 @@ class BFS(PathFindingStrategy): visited[hood] = visited[current] + 1 parents[hood] = current q.put(hood) - return self.restorePath(parents, start, exit) - - def restorePath(self, parents: dict, start: Cell, exit: Cell) -> list[Cell]|None: - path = [] - current = exit - while current: - path.append(current) - if current not in parents: - return None - current = parents[current] - return path[::-1] \ No newline at end of file + return restorePath(parents, start, exit) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/DFS.py b/MusinAA/task2/strategyObjects/DFS.py index 88f581c0..7a05d4df 100644 --- a/MusinAA/task2/strategyObjects/DFS.py +++ b/MusinAA/task2/strategyObjects/DFS.py @@ -1,8 +1,10 @@ from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy +from task2.strategyObjects.util import restorePath from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell + class DFS(PathFindingStrategy): """Поиск в глубину – быстрый, но не обязательно кратчайший. Возвращает None, если пути нет""" @@ -29,15 +31,4 @@ class DFS(PathFindingStrategy): parents[hood] = current stack.append(hood) - return self.restorePath(parents, start, exit) - - def restorePath(self, parents: dict, start: Cell, exit: Cell) -> list[Cell]|None: - path = [] - current = exit - while current: - path.append(current) - if current not in parents: - return None - current = parents[current] - return path[::-1] - \ No newline at end of file + return restorePath(parents, start, exit) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/util.py b/MusinAA/task2/strategyObjects/util.py new file mode 100644 index 00000000..8fd3c4d0 --- /dev/null +++ b/MusinAA/task2/strategyObjects/util.py @@ -0,0 +1,12 @@ +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.cell import Cell + +def restorePath(parents: dict, start: Cell, exit: Cell) -> list[Cell]|None: + path = [] + current = exit + while current: + path.append(current) + if current not in parents: + return None + current = parents[current] + return path[::-1] \ No newline at end of file From 107d5cbd61abf07aedcde09d3246f583af3882ab Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Wed, 20 May 2026 21:23:47 +0300 Subject: [PATCH 19/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=8D=D1=82=D0=B0=D0=BF=203,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20A*,=20=D0=BD?= =?UTF-8?q?=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/docs/Report 1.ipynb | 2 +- MusinAA/task2/strategyObjects/AStar.py | 38 ++++++++++++++++++- MusinAA/task2/strategyObjects/BFS.py | 2 +- MusinAA/task2/strategyObjects/DFS.py | 2 +- .../strategyObjects/pathFindingStrategy.py | 3 +- MusinAA/task2/strategyObjects/util.py | 2 +- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/MusinAA/docs/Report 1.ipynb b/MusinAA/docs/Report 1.ipynb index 3af91fe7..f476d565 100644 --- a/MusinAA/docs/Report 1.ipynb +++ b/MusinAA/docs/Report 1.ipynb @@ -140,7 +140,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.3" + "version": "3.14.5" } }, "nbformat": 4, diff --git a/MusinAA/task2/strategyObjects/AStar.py b/MusinAA/task2/strategyObjects/AStar.py index 7b1b45e6..5b4dde17 100644 --- a/MusinAA/task2/strategyObjects/AStar.py +++ b/MusinAA/task2/strategyObjects/AStar.py @@ -1,9 +1,45 @@ +import heapq +from itertools import count + from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy +from task2.strategyObjects.util import restorePath from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell class AStar(PathFindingStrategy): """Алгоритм с эвристикой (etc. манхэттенское расстояние) – компромисс между скоростью и оптимальностью.""" + def heuristic(self, first: Cell, second: Cell) -> int: + return abs(first.x - second.x) + abs(first.y - second.y) + def findPath(self, maze: Maze, start: Cell, exit: Cell): - ... \ No newline at end of file + tie_breaker = count() + start_heuristic = self.heuristic(start, exit) + heap: list[tuple[int, int, int, Cell]] = [ + (start_heuristic, start_heuristic, next(tie_breaker), start) + ] + g_score: dict[Cell, int] = {start: 0} + parents: dict[Cell, Cell | None] = {start: None} + visited: set[Cell] = set() + + while heap: + _, _, _, current = heapq.heappop(heap) + if current in visited: + continue + visited.add(current) + + if current == exit: + return restorePath(parents, exit) + + for neighbor in maze.getNeighbors(current): + tentative_score = g_score[current] + if tentative_score < g_score.get(neighbor, 10**12): + g_score[neighbor] = tentative_score + parents[neighbor] = current + heuristic = self.heuristic(neighbor, exit) + priority = tentative_score + heuristic + heapq.heappush( + heap, + (priority, heuristic, next(tie_breaker), neighbor), + ) + return [] \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/BFS.py b/MusinAA/task2/strategyObjects/BFS.py index d533fb85..6379a6a5 100644 --- a/MusinAA/task2/strategyObjects/BFS.py +++ b/MusinAA/task2/strategyObjects/BFS.py @@ -31,4 +31,4 @@ class BFS(PathFindingStrategy): visited[hood] = visited[current] + 1 parents[hood] = current q.put(hood) - return restorePath(parents, start, exit) \ No newline at end of file + return restorePath(parents, exit) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/DFS.py b/MusinAA/task2/strategyObjects/DFS.py index 7a05d4df..b4e0fb6c 100644 --- a/MusinAA/task2/strategyObjects/DFS.py +++ b/MusinAA/task2/strategyObjects/DFS.py @@ -31,4 +31,4 @@ class DFS(PathFindingStrategy): parents[hood] = current stack.append(hood) - return restorePath(parents, start, exit) \ No newline at end of file + return restorePath(parents, exit) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/pathFindingStrategy.py b/MusinAA/task2/strategyObjects/pathFindingStrategy.py index 0a1cb92e..800cb5bc 100644 --- a/MusinAA/task2/strategyObjects/pathFindingStrategy.py +++ b/MusinAA/task2/strategyObjects/pathFindingStrategy.py @@ -9,4 +9,5 @@ class PathFindingStrategy(ABC): @abstractmethod def findPath(self, maze: Maze, start: Cell, exit: Cell): - """Возвращает список клеток пути от старта до выхода включительно. Пути нет - пустой список.""" \ No newline at end of file + """Возвращает список клеток пути от старта до выхода включительно. Пути нет - пустой список.""" + raise NotImplementedError \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/util.py b/MusinAA/task2/strategyObjects/util.py index 8fd3c4d0..26dd7779 100644 --- a/MusinAA/task2/strategyObjects/util.py +++ b/MusinAA/task2/strategyObjects/util.py @@ -1,7 +1,7 @@ from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell -def restorePath(parents: dict, start: Cell, exit: Cell) -> list[Cell]|None: +def restorePath(parents: dict, exit: Cell) -> list[Cell]|None: path = [] current = exit while current: From 74928a997ac8de4458c0d23ae18a9374545f1bfa Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sat, 23 May 2026 12:02:30 +0300 Subject: [PATCH 20/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=AD=D1=82=D0=B0=D0=BF=205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/mazeObjects/path.py | 4 ++ MusinAA/task2/mazeSolver.py | 43 +++++++++++++++++++ MusinAA/task2/strategyObjects/AStar.py | 7 +-- MusinAA/task2/strategyObjects/BFS.py | 6 ++- MusinAA/task2/strategyObjects/DFS.py | 6 +-- .../strategyObjects/pathFindingStrategy.py | 3 +- 6 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 MusinAA/task2/mazeObjects/path.py create mode 100644 MusinAA/task2/mazeSolver.py diff --git a/MusinAA/task2/mazeObjects/path.py b/MusinAA/task2/mazeObjects/path.py new file mode 100644 index 00000000..9579dbc8 --- /dev/null +++ b/MusinAA/task2/mazeObjects/path.py @@ -0,0 +1,4 @@ +class Path: + def __init__(self, array:list[Cell]|None, visited_cells:int): + self.array = array + self.visited_cells = visited_cells \ No newline at end of file diff --git a/MusinAA/task2/mazeSolver.py b/MusinAA/task2/mazeSolver.py new file mode 100644 index 00000000..d346b366 --- /dev/null +++ b/MusinAA/task2/mazeSolver.py @@ -0,0 +1,43 @@ +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.cell import Cell +from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy + +import time + +class SearchStats: + """Время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути""" + def __init__(self, path: list[Cell]|None, duration:float, visited_cells:int, path_len:int, strategy_name:str): + self.duration = duration + self.visited_cells = visited_cells + self.path_len = path_len + self.path = path + self.strategy_name = strategy_name + +class MazeSolver: + """ + MazeSolver содержит поля maze и strategy. + Метод setStrategy(strategy) для динамической смены алгоритма. + Метод solve() вызывает strategy.findPath(...) и возвращает объект SearchStats (время выполнения в миллисекундах, + количество посещённых клеток, длина найденного пути). + Для замера времени используйте time.perf_counter() до и после вызова стратегии. + """ + + def __init__(self, maze:Maze, strategy:PathFindingStrategy): + self.maze = maze + self.strategy = strategy + + def setStrategy(self, strategy:PathFindingStrategy): + self.strategy = strategy + + def getStrategyName(self): + return self.strategy.__class__.__name__ + + def solve(self): + t_start = time.perf_counter() + path = self.strategy.findPath(self.maze, self.maze.startCell, self.maze.endCell) + duration = time.perf_counter() - t_start + + path_len = len(path.array) if path.array else 0 + strategy_name = self.getStrategyName() + + return SearchStats(path.array, duration, path.visited_cells, path_len, strategy_name) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/AStar.py b/MusinAA/task2/strategyObjects/AStar.py index 5b4dde17..43024bd4 100644 --- a/MusinAA/task2/strategyObjects/AStar.py +++ b/MusinAA/task2/strategyObjects/AStar.py @@ -6,13 +6,14 @@ from task2.strategyObjects.util import restorePath from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell +from task2.mazeObjects.path import Path class AStar(PathFindingStrategy): """Алгоритм с эвристикой (etc. манхэттенское расстояние) – компромисс между скоростью и оптимальностью.""" def heuristic(self, first: Cell, second: Cell) -> int: return abs(first.x - second.x) + abs(first.y - second.y) - def findPath(self, maze: Maze, start: Cell, exit: Cell): + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path: tie_breaker = count() start_heuristic = self.heuristic(start, exit) heap: list[tuple[int, int, int, Cell]] = [ @@ -29,7 +30,7 @@ class AStar(PathFindingStrategy): visited.add(current) if current == exit: - return restorePath(parents, exit) + return Path(restorePath(parents, exit), len(visited)) for neighbor in maze.getNeighbors(current): tentative_score = g_score[current] @@ -42,4 +43,4 @@ class AStar(PathFindingStrategy): heap, (priority, heuristic, next(tie_breaker), neighbor), ) - return [] \ No newline at end of file + return Path(None, len(visited)) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/BFS.py b/MusinAA/task2/strategyObjects/BFS.py index 6379a6a5..aa84fb94 100644 --- a/MusinAA/task2/strategyObjects/BFS.py +++ b/MusinAA/task2/strategyObjects/BFS.py @@ -3,13 +3,14 @@ from task2.strategyObjects.util import restorePath from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell +from task2.mazeObjects.path import Path import queue class BFS(PathFindingStrategy): """Поиск в ширину – гарантирует кратчайший путь по количеству шагов. Возвращает None, если пути нет""" - def findPath(self, maze: Maze, start: Cell, exit: Cell): + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path: visited = dict() parents = dict() q = queue.Queue() @@ -31,4 +32,5 @@ class BFS(PathFindingStrategy): visited[hood] = visited[current] + 1 parents[hood] = current q.put(hood) - return restorePath(parents, exit) \ No newline at end of file + + return Path(restorePath(parents, exit), len(visited)) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/DFS.py b/MusinAA/task2/strategyObjects/DFS.py index b4e0fb6c..e72dd009 100644 --- a/MusinAA/task2/strategyObjects/DFS.py +++ b/MusinAA/task2/strategyObjects/DFS.py @@ -3,12 +3,12 @@ from task2.strategyObjects.util import restorePath from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell - +from task2.mazeObjects.path import Path class DFS(PathFindingStrategy): """Поиск в глубину – быстрый, но не обязательно кратчайший. Возвращает None, если пути нет""" - def findPath(self, maze: Maze, start: Cell, exit: Cell): + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path: visited = dict() parents = dict() stack = [] @@ -31,4 +31,4 @@ class DFS(PathFindingStrategy): parents[hood] = current stack.append(hood) - return restorePath(parents, exit) \ No newline at end of file + return Path(restorePath(parents, exit), len(visited)) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/pathFindingStrategy.py b/MusinAA/task2/strategyObjects/pathFindingStrategy.py index 800cb5bc..94170657 100644 --- a/MusinAA/task2/strategyObjects/pathFindingStrategy.py +++ b/MusinAA/task2/strategyObjects/pathFindingStrategy.py @@ -2,12 +2,13 @@ from abc import ABC, abstractmethod from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell +from task2.mazeObjects.path import Path class PathFindingStrategy(ABC): """Интерфейс PathFindingStrategy с методом findPath(maze, start, exit), возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет.""" @abstractmethod - def findPath(self, maze: Maze, start: Cell, exit: Cell): + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path: """Возвращает список клеток пути от старта до выхода включительно. Пути нет - пустой список.""" raise NotImplementedError \ No newline at end of file From 95805467ab9d831ec52b80d0eca2f6e2b92e541c Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sun, 24 May 2026 00:33:24 +0300 Subject: [PATCH 21/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BF=D0=B0=D1=82=D1=82=D0=B5=D1=80?= =?UTF-8?q?=D0=BD=20Observer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/main.py | 20 +++++++ MusinAA/task2/consoleView.py | 94 ++++++++++++++++++++++++++++++ MusinAA/task2/mazeBuilder.py | 17 +++--- MusinAA/task2/mazeExamples/low.txt | 5 ++ MusinAA/task2/mazeObjects/maze.py | 4 +- MusinAA/task2/mazeObjects/path.py | 2 + MusinAA/task2/mazeSolver.py | 27 ++++++--- MusinAA/task2/observerSubject.py | 43 ++++++++++++++ 8 files changed, 193 insertions(+), 19 deletions(-) create mode 100644 MusinAA/main.py create mode 100644 MusinAA/task2/consoleView.py create mode 100644 MusinAA/task2/mazeExamples/low.txt create mode 100644 MusinAA/task2/observerSubject.py diff --git a/MusinAA/main.py b/MusinAA/main.py new file mode 100644 index 00000000..752e187d --- /dev/null +++ b/MusinAA/main.py @@ -0,0 +1,20 @@ +from task2.consoleView import ConsoleView +from task2.mazeBuilder import TextFileMazeBuilder +from task2.mazeSolver import MazeSolver + +from task2.strategyObjects.BFS import BFS +from task2.strategyObjects.DFS import DFS +from task2.strategyObjects.AStar import AStar + +console = ConsoleView() +builder = TextFileMazeBuilder() +solver = MazeSolver(BFS()) +solver.attach(console) + +maze = builder.buildFromFile("task2/mazeExamples/low.txt") +console.maze = maze # хмммм +solver.setMaze(maze) + +input() + +solver.solve() diff --git a/MusinAA/task2/consoleView.py b/MusinAA/task2/consoleView.py new file mode 100644 index 00000000..50b1a4ad --- /dev/null +++ b/MusinAA/task2/consoleView.py @@ -0,0 +1,94 @@ +""" +Реализовать класс ConsoleView, который отображает лабиринт, +текущее положение игрока (если реализован пошаговый режим) и найденный путь. +Метод render(maze, player_position, path) рисует карту в консоли.""" + +import os + +from task2.mazeObjects.cell import Cell +from task2.mazeObjects.maze import Maze +from task2.mazeObjects.path import Path +from task2.observerSubject import MazeEvent, MazeEventType, Observer + +SBROS = "\033[0m" + +WALL = "#" +EXIT = "E" +START = "S" +PATH_SYMBOL = " " +SPACE_SYMBOL = " " + +# Я убрал аргументы из render(), чтобы не передавать их при каждом запуске. +# И работать внутри класса приятнее, чем тянуть эти аргументы туда-сюда +class ConsoleView(Observer): + maze:Maze|None + path:Path|None + + def __init__(self, maze:Maze|None=None, path:Path|None=None): + super().__init__() + self.maze = maze + self.path = path + + def _getCellColored(self, cell:Cell) -> str: + if cell.isWall: + # Белый + return self._fmt_str(7, 7, WALL) + elif cell.isExit: + # Кислотно-зелёный + return self._fmt_str(12, 7, EXIT) + elif cell.isStart: + # Кислотно-красный + return self._fmt_str(9, 7, START) + elif self.path and self.path.array: + if cell in self.path.array: + # Градиент + percent = self.path.array.index(cell) / len(self.path.array) + n = self._ANSICalculator(*self._getGradient(percent)) + return self._fmt_str(n, n, PATH_SYMBOL) + return SPACE_SYMBOL + + def _fmt_str(self, bg:int, fg:int, symbol:str) -> str: + return f"\033[48;5;{bg}m\033[38;5;{fg}m{symbol}{SBROS}" + + def _ANSICalculator(self, r:int, g:int, b:int): + r = max(0, min(5, r)) + g = max(0, min(5, g)) + b = max(0, min(5, b)) + return 16 + 36*r + 6*g + b + + def _getGradient(self, percent:float): + r = 5 * (1-percent) + g = 0 + b = 5 * percent + return int(round(r)), int(round(g)), int(round(b)) + + def render(self, player_position=None): + """ + Печатем ячейку. + Цвет зависит от индекса ячейчки в массиве path. + Если в массиве нет - просто белый. + """ + + os.system('cls' if os.name == 'nt' else 'clear') + + if not self.maze: + print("Лабиринт ещё не загружен") + return None + + output = "" + for y in range(self.maze.height): + for x in range(self.maze.width): + cell = self.maze.getCell(x, y) + output += self._getCellColored(cell) + output += "\n" + print(output) + + def update(self, event: MazeEvent): + if event.evtype in (MazeEventType.MAZE_LOADED, MazeEventType.PATH_FOUND, MazeEventType.MOVE): + if event.evtype == MazeEventType.PATH_FOUND: + if not event.data: raise ValueError + self.path = event.data + if event.evtype == MazeEventType.MAZE_LOADED: + if not event.data: raise ValueError + self.maze = self.maze + self.render() diff --git a/MusinAA/task2/mazeBuilder.py b/MusinAA/task2/mazeBuilder.py index 7ddcc0f2..1fcec597 100644 --- a/MusinAA/task2/mazeBuilder.py +++ b/MusinAA/task2/mazeBuilder.py @@ -4,15 +4,14 @@ import sys from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell +from task2.observerSubject import MazeEvent, MazeEventType, Subject class MazeBuilder(ABC): """Интерфейс MazeBuilder с методом buildFromFile(filename)""" - @abstractmethod def buildFromFile(self, filename: str): """Создание лабиринта из файла.""" - class TextFileMazeBuilder(MazeBuilder): """Читает файл, парсит символы, создаёт объекты Cell, @@ -22,7 +21,7 @@ class TextFileMazeBuilder(MazeBuilder): start = {'x': 0, 'y': 0} end = {'x': 0, 'y': 0} - def cellStrategy(self, letter: str) -> Cell: + def _cellStrategy(self, letter: str) -> Cell: if letter == '#': return Cell(isWall=True) elif letter == ' ': @@ -35,13 +34,13 @@ class TextFileMazeBuilder(MazeBuilder): sys.stderr.write(f"Неизвестный символ '{letter}' при загрузке из файла\n") return Cell() - def updateStartEnd(self, letter: str, x:int, y:int) -> None: + def _updateStartEnd(self, letter: str, x:int, y:int) -> None: if letter == 'S': self.start = {'x': x, 'y': y} elif letter == 'E': self.end = {'x': x, 'y': y} - def generate_row_from_txt(self, filename: str) -> list[str]: + def _generate_row_from_txt(self, filename: str) -> list[str]: with open(filename) as file: text = file.read() text = text.strip() @@ -51,16 +50,15 @@ class TextFileMazeBuilder(MazeBuilder): return text def buildFromFile(self, filename: str): - rows = self.generate_row_from_txt(filename) + rows = self._generate_row_from_txt(filename) height = len(rows) width = len(rows[0]) array = [[Cell() for j in range(width)] for i in range(height)] - # Здесь x и y где-то перепутаны, но мне лень это чинить try: for x, y in product(range(width), range(height)): - cell = self.cellStrategy(rows[y][x]) - self.updateStartEnd(rows[y][x], x, y) + cell = self._cellStrategy(rows[y][x]) + self._updateStartEnd(rows[y][x], x, y) cell.x = x cell.y = y array[y][x] = cell @@ -68,5 +66,4 @@ class TextFileMazeBuilder(MazeBuilder): raise ValueError(f"Строка {x+1} имеет длину {len(rows[x])}, ожидалось {width}") return Maze(array, self.start, self.end) - \ No newline at end of file diff --git a/MusinAA/task2/mazeExamples/low.txt b/MusinAA/task2/mazeExamples/low.txt new file mode 100644 index 00000000..28f10587 --- /dev/null +++ b/MusinAA/task2/mazeExamples/low.txt @@ -0,0 +1,5 @@ +##### +# S # +# ### +# E +##### \ No newline at end of file diff --git a/MusinAA/task2/mazeObjects/maze.py b/MusinAA/task2/mazeObjects/maze.py index a9aae9dc..301d77d0 100644 --- a/MusinAA/task2/mazeObjects/maze.py +++ b/MusinAA/task2/mazeObjects/maze.py @@ -9,8 +9,8 @@ class Maze: def __init__(self, mazeArray: list[list[Cell]], start: dict, end: dict) -> None: self.mazeArray = mazeArray - self.height = len(mazeArray) - self.width = len(mazeArray[0]) + self.height = len(mazeArray) # X + self.width = len(mazeArray[0]) # Y self.startCell = self.getCell(start['x'], start['y']) self.endCell = self.getCell(end['x'], end['y']) diff --git a/MusinAA/task2/mazeObjects/path.py b/MusinAA/task2/mazeObjects/path.py index 9579dbc8..3277c73a 100644 --- a/MusinAA/task2/mazeObjects/path.py +++ b/MusinAA/task2/mazeObjects/path.py @@ -1,3 +1,5 @@ +from task2.mazeObjects.cell import Cell + class Path: def __init__(self, array:list[Cell]|None, visited_cells:int): self.array = array diff --git a/MusinAA/task2/mazeSolver.py b/MusinAA/task2/mazeSolver.py index d346b366..6d28cdc5 100644 --- a/MusinAA/task2/mazeSolver.py +++ b/MusinAA/task2/mazeSolver.py @@ -1,6 +1,9 @@ from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell +from task2.observerSubject import MazeEvent, MazeEventType, Subject from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy +from task2.strategyObjects.BFS import BFS + import time @@ -13,7 +16,7 @@ class SearchStats: self.path = path self.strategy_name = strategy_name -class MazeSolver: +class MazeSolver(Subject): """ MazeSolver содержит поля maze и strategy. Метод setStrategy(strategy) для динамической смены алгоритма. @@ -22,10 +25,15 @@ class MazeSolver: Для замера времени используйте time.perf_counter() до и после вызова стратегии. """ - def __init__(self, maze:Maze, strategy:PathFindingStrategy): - self.maze = maze + def __init__(self, strategy:PathFindingStrategy, maze:Maze|None=None): + super().__init__() + self._maze = maze self.strategy = strategy + def setMaze(self, maze: Maze|None): + self._maze = maze + self.notify(MazeEvent(MazeEventType.MAZE_LOADED, data=maze)) + def setStrategy(self, strategy:PathFindingStrategy): self.strategy = strategy @@ -33,11 +41,16 @@ class MazeSolver: return self.strategy.__class__.__name__ def solve(self): - t_start = time.perf_counter() - path = self.strategy.findPath(self.maze, self.maze.startCell, self.maze.endCell) - duration = time.perf_counter() - t_start + if not self._maze: + raise ValueError + t_start = time.perf_counter() + path = self.strategy.findPath(self._maze, self._maze.startCell, self._maze.endCell) + duration = time.perf_counter() - t_start + path_len = len(path.array) if path.array else 0 strategy_name = self.getStrategyName() - return SearchStats(path.array, duration, path.visited_cells, path_len, strategy_name) \ No newline at end of file + stats = SearchStats(path.array, duration, path.visited_cells, path_len, strategy_name) + self.notify(MazeEvent(MazeEventType.PATH_FOUND, data=path)) + return stats \ No newline at end of file diff --git a/MusinAA/task2/observerSubject.py b/MusinAA/task2/observerSubject.py new file mode 100644 index 00000000..c49b8f83 --- /dev/null +++ b/MusinAA/task2/observerSubject.py @@ -0,0 +1,43 @@ +""" +Создать интерфейс Observer с методом update(event), +где event может быть строкой или объектом с типом события ("path_found", "move", "maze_loaded"). +""" + +from enum import Enum +from abc import ABC, abstractmethod + +class MazeEventType(Enum): + PATH_FOUND = "path_found" + MOVE = "move" + MAZE_LOADED = "maze_loaded" + +class MazeEvent: + data=None + def __init__(self, evtype: MazeEventType, data=None): + if not isinstance(evtype, MazeEventType): + raise TypeError(f"evtype must be an EventType, got {type(evtype)}") + self.evtype = evtype + self.data = data + +class Observer(ABC): + @abstractmethod + def update(self, event: MazeEvent): + raise NotImplementedError + + +class Subject(ABC): + """Издатель: управляет подписчиками и отправляет им уведомления.""" + def __init__(self): + self._observers:set[Observer] = set() + + def attach(self, obs:Observer): + "Подписать наблюдателя" + self._observers.add(obs) + + def detach(self, obs:Observer): + "Отписать наблюдателя" + self._observers.discard(obs) + + def notify(self, event:MazeEvent): + for obs in self._observers: + obs.update(event) \ No newline at end of file From 983ef65bef500a6aad09a716365747c8525879df Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sun, 24 May 2026 00:45:43 +0300 Subject: [PATCH 22/24] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D0=BE=D0=BD=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=8D=D1=82=D0=B0=D0=BF=205.=20=D0=9F=D0=B0?= =?UTF-8?q?=D1=82=D1=82=D0=B5=D1=80=D0=BD=20Command=20=D0=B1=D1=83=D0=B4?= =?UTF-8?q?=D0=B5=D1=82=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BF=D0=BE=D0=B7=D0=B6=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/task2/consoleView.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MusinAA/task2/consoleView.py b/MusinAA/task2/consoleView.py index 50b1a4ad..6192d6ee 100644 --- a/MusinAA/task2/consoleView.py +++ b/MusinAA/task2/consoleView.py @@ -15,7 +15,7 @@ SBROS = "\033[0m" WALL = "#" EXIT = "E" START = "S" -PATH_SYMBOL = " " +PATH_SYMBOL = "+" SPACE_SYMBOL = " " # Я убрал аргументы из render(), чтобы не передавать их при каждом запуске. @@ -44,7 +44,7 @@ class ConsoleView(Observer): # Градиент percent = self.path.array.index(cell) / len(self.path.array) n = self._ANSICalculator(*self._getGradient(percent)) - return self._fmt_str(n, n, PATH_SYMBOL) + return self._fmt_str(n, 10, PATH_SYMBOL) return SPACE_SYMBOL def _fmt_str(self, bg:int, fg:int, symbol:str) -> str: From 9849075a381ac519cb11acd5517f8cb47283c61d Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Sun, 24 May 2026 19:12:04 +0300 Subject: [PATCH 23/24] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81-?= =?UTF-8?q?=D0=BE=D1=80=D0=BA=D0=B5=D1=81=D1=82=D1=80=D0=B0=D1=82=D0=BE?= =?UTF-8?q?=D1=80=20=D0=B4=D0=BB=D1=8F=20=D1=82=D0=B5=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/docs/data/mazeRezults.csv | 13 +++ MusinAA/main.py | 20 ---- MusinAA/task2/.gitignore | 1 + MusinAA/task2/mazeExamples/maze_100x100.txt | 100 ++++++++++++++++++++ MusinAA/task2/mazeExamples/maze_10x10.txt | 10 ++ MusinAA/task2/mazeExamples/maze_50x50.txt | 50 ++++++++++ MusinAA/task2/mazeSolver.py | 12 ++- MusinAA/task2/tester.py | 83 ++++++++++++++++ 8 files changed, 268 insertions(+), 21 deletions(-) create mode 100644 MusinAA/docs/data/mazeRezults.csv delete mode 100644 MusinAA/main.py create mode 100644 MusinAA/task2/.gitignore create mode 100644 MusinAA/task2/mazeExamples/maze_100x100.txt create mode 100644 MusinAA/task2/mazeExamples/maze_10x10.txt create mode 100644 MusinAA/task2/mazeExamples/maze_50x50.txt create mode 100644 MusinAA/task2/tester.py diff --git a/MusinAA/docs/data/mazeRezults.csv b/MusinAA/docs/data/mazeRezults.csv new file mode 100644 index 00000000..ad636c75 --- /dev/null +++ b/MusinAA/docs/data/mazeRezults.csv @@ -0,0 +1,13 @@ +Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути +BFS,50x50,3.2134529003087664,720,431 +BFS,10x10,0.2288821000547614,53,23 +BFS,5x5,0.035640300302475225,8,7 +BFS,100x100,11.366462300065905,2495,1171 +DFS,50x50,1.7320569000730757,747,431 +DFS,10x10,0.0645840002107434,35,31 +DFS,5x5,0.015984299898264,8,7 +DFS,100x100,8.414763500331901,3219,1243 +AStar,50x50,1.8410825001410558,509,455 +AStar,10x10,0.1062644998455653,23,23 +AStar,5x5,0.03181890006089816,8,7 +AStar,100x100,6.592893099877983,1286,1171 diff --git a/MusinAA/main.py b/MusinAA/main.py deleted file mode 100644 index 752e187d..00000000 --- a/MusinAA/main.py +++ /dev/null @@ -1,20 +0,0 @@ -from task2.consoleView import ConsoleView -from task2.mazeBuilder import TextFileMazeBuilder -from task2.mazeSolver import MazeSolver - -from task2.strategyObjects.BFS import BFS -from task2.strategyObjects.DFS import DFS -from task2.strategyObjects.AStar import AStar - -console = ConsoleView() -builder = TextFileMazeBuilder() -solver = MazeSolver(BFS()) -solver.attach(console) - -maze = builder.buildFromFile("task2/mazeExamples/low.txt") -console.maze = maze # хмммм -solver.setMaze(maze) - -input() - -solver.solve() diff --git a/MusinAA/task2/.gitignore b/MusinAA/task2/.gitignore new file mode 100644 index 00000000..a548902a --- /dev/null +++ b/MusinAA/task2/.gitignore @@ -0,0 +1 @@ +maze_generator.py \ No newline at end of file diff --git a/MusinAA/task2/mazeExamples/maze_100x100.txt b/MusinAA/task2/mazeExamples/maze_100x100.txt new file mode 100644 index 00000000..cadb849b --- /dev/null +++ b/MusinAA/task2/mazeExamples/maze_100x100.txt @@ -0,0 +1,100 @@ +S # # # # # # # # # # # # # # + # # ####### ### # ##### # # # # # # # # ### # # ### # # ### # ##### # ##### ### ### ### ######### # + # # # # # # # # # # # # # # # # # # # # # # # # # # + # ##### ##### ### ### ##### ##### ####### # ##### ####### # # # ####### ##### # ######### ### ### # + # # # # # # # # # # # # # # # # # # # # # # # # # + # # ##### # # ##### # # ####### # ### ##### # # # # ### ### ######### ##### # ##### # # ### ### # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + ##### # # # ### ### ####### # # ### ######### ### ### ### ### # # # # # ### ##### # # ### # # # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # + ### # ### # ##### ##### ######### ######### # ##### ### # ####### # ##### ### ####### # ### # ### # + # # # # # # # # # # # # # # # # # # # # # + # ################# # ### # # # # # ########### ############# # ##### ##### ##### # ##### ### # ### + # # # # # # # # # # # # # # # # # # # # # # + ### # ### # ### ####### # ##### ######### ### # ### ### # ####### # ### ####### ##### # ### # # # # + # # # # # # # # # # # # # # # # # # # # # # # + ######### ### ### # ### # ####### ##### # # # ####### ##### ####### # ########### ####### ######### + # # # # # # # # # # # # # # # # # # # # # +## # ####### ### ##### # ####### ### # # # # ##### ### ########### # # # ### # ##### # # ### ##### # + # # # # # # # # # # # # # # # # # # # # # # # # # + ### # # ########### ######### # ### # # ### ### ####### ### # # # # ##### # ### ##### # # ### ### # + # # # # # # # # # # # # # # # # # # # # # # # # # # # +## ##### # # # # # ### # ### ##### # # # ##### # ### # ### # # # # # # # ##### ### ####### # ### ### + # # # # # # # # # # # # # # # # # # # # # # # # # # # # + ##### ### # # # # # ######### ### ### ### # ##### ##### ### # ##### # ######### ############# ### # + # # # # # # # # # # # # # # # # # # # # # # # # + # ##### # # ### ### ##### # ### ### ##### # # ### # ##### ##### ### ### # ##### ### # # ### # # ### + # # # # # # # # # # # # # # # # # # # # # # # # # # + # # # ####### ####### ### ### ######### ### # # ##### # ### ########### ### # ### ####### ### # # # + # # # # # # # # # # # # # # # # # # # # # # + ################### # # # # ### # # # ######### # # ### ############### # ### ##### ### ### ##### # + # # # # # # # # # # # # # # # # # # # # # + ##### # # ### # # ### # ####### # # ##### # # ### ##### # ####### # # # ######### # ######### ##### + # # # # # # # # # # # # # # # # # # # # # # # # # # # # +#### # # ### # # ### # ### ####### ##### # # ### # # ##### # # ####### # # # ##### ### # # ####### # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +## # # # # ### ### # ### ### # # # ### # ####### ### # ########### # ##### ##### # # ### ### ##### # + # # # # # # # # # # # # # # # # # # # # # # # # # # + ########### # # ########### ### # # # # # ### # # ### # ####### # ######### # # ### # ######### ### + # # # # # # # # # # # # # # # # # # # # # # # # +######## # # # ### # ########### # # ####### ### # # # ####### ### # # # ### # ### ######### # # # # + # # # # # # # # # # # # # # # # # # # # # # # + ##### ##### ### # # # ####### ### ### # # # # ############# # ##### # ### ##### ######### ### ### # + # # # # # # # # # # # # # # # # # # # # # # # # # + ### ### # ### # ##### # # # ### # # # ### # ### ### # # # # ########### ####### # ##### ####### ### + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +## ####### # ##### # ### ### # # # # # # # ####### ### # # # # # # # # ### # # ##### # # # # ##### # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + ### # # # ##### ####### # ### # ### ### ####### # # ### ####### ### ####### # ### ####### # # # # # + # # # # # # # # # # # # # # # # # # # # # # # # + # ##### ##### # # ### ### # ######### ### ### ### ### ####### ##### # ######### ##### # ### # ### # + # # # # # # # # # # # # # # # # # # # # # # # # # + # # ####### # ##### ### # # # # # ### ### # ### ### ######### # ####### # ####### ### ######### ### + # # # # # # # # # # # # # # # # # # # # # # # # # # # + ######### # ### ### # # # # # # ####### ##### ### # # ##### # ### # # ####### # # # ### # # # # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # +## # # # ### ##### # ######### # # ####### ### ### # ##### ######### # # # ####### ### # # # # ### # + # # # # # # # # # # # # # # # # # # # # # # # + ### ######### ####### # # ######### # # ### ### ### ##### ### # ########### ######### # # # ### ### + # # # # # # # # # # # # # # # # # # # # # # # # +## ######### # ### # ####### ### # ### ### # # ####### # ### ##### ### # # ### # ### ##### # # ### # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # # ### ### ### # # # ##### # ### # ### ### # # # ####### ##### ### ### # ##### # ##### # ### # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # # # ### ### # ### # ######### # ######### # ### # # # ### ##### ### # ### # # ### # ##### ##### # + # # # # # # # # # # # # # # # # # # # # # # # # # # + ##### # ### ##### ####### ### # ##### # # # ### ### # ##### # # ### # ####### ##### # # # ### # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # + ### # ### ######### # ##### ### # ### # # # ############# ### ### # ##### # ### ######### # ### # # + # # # # # # # # # # # # # # # # # # # # # # # # +## # # # ### ##### # ##### # ### ### # # # ### # # ######### # # ##### ### ####### ### # ######### # + # # # # # # # # # # # # # # # # # # # # # # # # # # # + # ############### ### ####### ### # ### ####### # # # # ######### # ### # # # ##### ##### # # ##### + # # # # # # # # # # # # # # # # # # # # # # + ##### # # ### # ### ######### # ######### # # ### # # ####### # ########### # # # ##### ####### # # + # # # # # # # # # # # # # # # # # # # # # # # +## # ####### ### # # # ############# ### ### ### ### ##### # # ### ########### ##### # ### ### # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # + ### ### # # # ##### ### # # ##### ####### # # ### ### # # ##### # # ####### ##### # # # # # ##### # + # # # # # # # # # # # # # # # # # # # # # # # # # # # +###### ##### ### # ### ### # # ########### # # # ##### # ### ##### # # ### ######### ### ##### # ### + # # # # # # # # # # # # # # # # # # # # # # # + # # # # # ### ####### ######### # # ######### ### # ##### ##### ##### ### # # # # # # ### ####### # + # # # # # # # # # # # # # # # # # # # # # # # # +#### # ##### # # # ##### ### # ### # ##### ### # ##### ### ######### ### ### ########### # # ##### # + # # # # # # # # # # # # # # # # # # # # # # # # # # # + ######### ##### # ####### # # # ##### # ### # # # # ####### ##### # # ### ### # # # # ### # # ### # + # # # # # # # # # # # # # # # # # # # # # # # # # # + # ### # ##### # # # ############### ### ######### ### # ##### # # ######### # ### # ### # # # # ### + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # # # ####### # ### # ##### ##### # # ### # ### # # ##### # ### # # # ####### # ##### # # # ### # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # + ##### # ### # # # ##### ##### # # # ### ### # # # # ######### # ########### # # ##### ####### # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # +## # # # # ##### # ### ####### ########### ### # # ####### # # ##### # # # ### ##### ##### # ##### # + # # # # # # # # # # # # # # # # # # # # # # + ### ##### ########### # ### # # ####### # # # ############# # ### ### # ### ######### # ### ### # # + # # # # # # # # # # # # # # # # # # # # # # # # # + ##### # # # ##### ####### # ### # # ##### # ### ####### ######### # ########### ### # ######### # # + # # # # # # # # # +################################################################################################## E diff --git a/MusinAA/task2/mazeExamples/maze_10x10.txt b/MusinAA/task2/mazeExamples/maze_10x10.txt new file mode 100644 index 00000000..3633a552 --- /dev/null +++ b/MusinAA/task2/mazeExamples/maze_10x10.txt @@ -0,0 +1,10 @@ +S # + ### ### # + # # # +## # # ### + # # # + ####### # + # # +## # ##### + +######## E diff --git a/MusinAA/task2/mazeExamples/maze_50x50.txt b/MusinAA/task2/mazeExamples/maze_50x50.txt new file mode 100644 index 00000000..eea2134c --- /dev/null +++ b/MusinAA/task2/mazeExamples/maze_50x50.txt @@ -0,0 +1,50 @@ +S # # # # + ####### ### # # ########### ##### # ##### ##### # + # # # # # # # # # # +## # # ### ####### ##### ####### # ### ######### # + # # # # # # # # # # # # # + ####### # # # # ### ##### # ### ### # # # # ### # + # # # # # # # # # # # # # # # # # + # ### # ### # ### ### # # ### ### ####### # # ### + # # # # # # # # # # # # # # + # # ############# # ### ### # ######### # # ### # + # # # # # # # # # + ########### ########### # ##### ### ### # # # ### + # # # # # # # # # # # # # # + # # ####### # ### # ##### ### ### ### ### # # # # + # # # # # # # # # # # # # # # # +###### ### ### # # # # # # # ### ### ##### # # # # + # # # # # # # # # # # # # # # + ### # # ######### ### # # # # ####### ##### # # # + # # # # # # # # # # # # # # + ### ### # ##### # # ######### # # # # ##### # # # + # # # # # # # # # # # # +## # ######### # # ### ### # ### ######### ##### # + # # # # # # # # # # # # + ##### # ### # ### ##### # # # ####### ##### # # # + # # # # # # # # # # # # # # # +## # ##### # # ##### ##### ### ### # ### # # # ### + # # # # # # # # # # # # # + ##### # ### # # ##### ### # ### ######### # ##### + # # # # # # # # # # # + # ####### ######### ### ####### # # ####### ### # + # # # # # # # # # # # # # # + # # ####### # # ##### # # ### ### # # # # ##### # + # # # # # # # # # # # # # # # # + # ##### # ####### # # # # # ### # ### # # # ### # + # # # # # # # # # # # # # # # + # ########### # ### ####### ### # ### # # # # # # + # # # # # # # # # # # # # + # # ####### ##### ########### ##### # # ##### # # + # # # # # # # # # # # + ### ### ### # ############### # # # ##### ### ### + # # # # # # # # # # # # # # + # ### ### # ### ##### # # # # # ##### # ### # # # + # # # # # # # # # # # # # # + # # ####### # ### ######### ######### ### # # # # + # # # # # # # # # # # # # # # + ##### # ####### # # # ### # # # # # ### ### # # # + # # # # # # # # # # # # # # # +## ### ##### ####### ### # # ### ##### # ### ### # + # # # # +################################################ E diff --git a/MusinAA/task2/mazeSolver.py b/MusinAA/task2/mazeSolver.py index 6d28cdc5..6d380e3b 100644 --- a/MusinAA/task2/mazeSolver.py +++ b/MusinAA/task2/mazeSolver.py @@ -8,6 +8,7 @@ from task2.strategyObjects.BFS import BFS import time class SearchStats: + maze_name:str = "None" """Время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути""" def __init__(self, path: list[Cell]|None, duration:float, visited_cells:int, path_len:int, strategy_name:str): self.duration = duration @@ -15,6 +16,15 @@ class SearchStats: self.path_len = path_len self.path = path self.strategy_name = strategy_name + + def toDict(self,): + return { + "strategy_name" : self.strategy_name, + "maze_name" : self.maze_name, + "duration" : self.duration, + "visited_cells" : self.visited_cells, + "path_len" : self.path_len + } class MazeSolver(Subject): """ @@ -46,7 +56,7 @@ class MazeSolver(Subject): t_start = time.perf_counter() path = self.strategy.findPath(self._maze, self._maze.startCell, self._maze.endCell) - duration = time.perf_counter() - t_start + duration = (time.perf_counter() - t_start) * 1000 path_len = len(path.array) if path.array else 0 strategy_name = self.getStrategyName() diff --git a/MusinAA/task2/tester.py b/MusinAA/task2/tester.py new file mode 100644 index 00000000..1abf6db6 --- /dev/null +++ b/MusinAA/task2/tester.py @@ -0,0 +1,83 @@ +from task2.mazeBuilder import MazeBuilder +from task2.mazeObjects.maze import Maze +from task2.mazeSolver import MazeSolver, SearchStats + +from task2.strategyObjects.BFS import BFS +from task2.strategyObjects.DFS import DFS +from task2.strategyObjects.AStar import AStar + +import csv +import os + +TEST_ITERATIONS = 10 + +class Tester(): + """Для каждого лабиринта и каждой стратегии запустить solve() 5–10 раз, + усреднить время, количество посещённых клеток, длину пути. + Записать результаты в CSV: + лабиринт,стратегия,время_мс,посещено_клеток,длина_пути.""" + result:list[SearchStats] + def __init__(self, builder:MazeBuilder, writefile:str): + self.builder = builder + self.writefile = writefile + + def setTestingDirectory(self, directory:str): + if directory[-1] != "/": + directory += "/" + self._directory = directory + + def _getMazes(self) -> list[Maze]: + arr = [] + files = os.listdir(self._directory) + only_txt_files = [f for f in files if os.path.isfile(os.path.join(self._directory, f)) and os.path.splitext(f)[1] == ".txt"] + + for f in only_txt_files: + arr.append(builder.buildFromFile(os.path.join(self._directory, f))) + return arr + + def _solveAvg(self, solver: MazeSolver): + avgtime = 0 + for i in range(TEST_ITERATIONS): + result = solver.solve() + # Всё кроме времени будет одинаковым + avgtime += result.duration/TEST_ITERATIONS + result.duration = avgtime + return result + + def saveCSV(self): + rows = [] + for r in self.result: + r = r.toDict() + row = (r["strategy_name"], + r["maze_name"], + r["duration"], + r["visited_cells"], + r["path_len"] + ) + rows.append(row) + with open(self.writefile, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["Алгоритм", "Лабиринт", "Время (мс)", "Посещённые клетки", "Длинна пути"]) + writer.writerows(rows) + + def test(self): + self.result = [] + arr = self._getMazes() + for algoritm in (BFS, DFS, AStar): + solver = MazeSolver(algoritm()) # это прикол + for maze in arr: + solver.setMaze(maze) + self.result.append(self._solveAvg(solver)) + self.result[-1].maze_name = f"{maze.height}x{maze.width}" + return self.result + + +if __name__ == "__main__": + from task2.mazeBuilder import TextFileMazeBuilder + + builder = TextFileMazeBuilder() + tester = Tester(builder, "docs/data/mazeRezults.csv") + tester.setTestingDirectory("task2/mazeExamples") + tester.test() + tester.saveCSV() + \ No newline at end of file From 3d9390a894dee1f7aaac656cf997e405ca76e961 Mon Sep 17 00:00:00 2001 From: oSTEVEo Date: Mon, 25 May 2026 03:00:00 +0300 Subject: [PATCH 24/24] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=B2=D1=81=D0=B5=D0=B3=D0=BE?= =?UTF-8?q?=20=D0=B8=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BE?= =?UTF-8?q?=D1=82=D1=87=D1=91=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusinAA/docs/Report 2.ipynb | 903 ++++++++++++++++++ MusinAA/docs/data/mazeRezults.csv | 13 - MusinAA/docs/data/task2/results.csv | 16 + MusinAA/docs/data/task2/results2.csv | 7 + MusinAA/task2/mazeBuilder.py | 11 +- .../{maze_100x100.txt => 100x100.txt} | 0 .../{maze_10x10.txt => 10x10.txt} | 0 MusinAA/task2/mazeExamples/25x25.txt | 25 + MusinAA/task2/mazeExamples/50x50.txt | 50 + .../task2/mazeExamples/{low.txt => 5x5.txt} | 0 MusinAA/task2/mazeExamples/maze_50x50.txt | 50 - .../mazeExamplesSpeical/maze_25x25_empty.txt | 25 + .../maze_25x25_wo_exit.txt | 25 + MusinAA/task2/mazeObjects/maze.py | 3 +- MusinAA/task2/mazeSolver.py | 2 +- MusinAA/task2/strategyObjects/AStar.py | 2 +- MusinAA/task2/strategyObjects/BFS.py | 11 +- MusinAA/task2/strategyObjects/DFS.py | 11 +- MusinAA/task2/tester.py | 13 +- 19 files changed, 1087 insertions(+), 80 deletions(-) create mode 100644 MusinAA/docs/Report 2.ipynb delete mode 100644 MusinAA/docs/data/mazeRezults.csv create mode 100644 MusinAA/docs/data/task2/results.csv create mode 100644 MusinAA/docs/data/task2/results2.csv rename MusinAA/task2/mazeExamples/{maze_100x100.txt => 100x100.txt} (100%) rename MusinAA/task2/mazeExamples/{maze_10x10.txt => 10x10.txt} (100%) create mode 100644 MusinAA/task2/mazeExamples/25x25.txt create mode 100644 MusinAA/task2/mazeExamples/50x50.txt rename MusinAA/task2/mazeExamples/{low.txt => 5x5.txt} (100%) delete mode 100644 MusinAA/task2/mazeExamples/maze_50x50.txt create mode 100644 MusinAA/task2/mazeExamplesSpeical/maze_25x25_empty.txt create mode 100644 MusinAA/task2/mazeExamplesSpeical/maze_25x25_wo_exit.txt diff --git a/MusinAA/docs/Report 2.ipynb b/MusinAA/docs/Report 2.ipynb new file mode 100644 index 00000000..d0ec0482 --- /dev/null +++ b/MusinAA/docs/Report 2.ipynb @@ -0,0 +1,903 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6dc3ad27", + "metadata": {}, + "source": [ + "# Отчёт: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)\n", + "\n", + "- Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid).\n", + "- Результаты экспериментов (таблицы, графики).\n", + "- Анализ эффективности алгоритмов и применимости паттернов.\n", + "- Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них." + ] + }, + { + "cell_type": "markdown", + "id": "59b2f0d3", + "metadata": {}, + "source": [ + "## 1. Описание задачи и выбранных паттернов\n", + "\n", + "Необходимо создать программу с применением паттернов ООП для решения задачи поиска из лабиринта. Для этого, подготовим схему из Mermaid, отражающую связи между объектами:\n", + "\n", + "```mermaid\n", + "classDiagram\n", + " class Maze {\n", + " -Cell[] cells\n", + " -int width, height\n", + " -Cell start\n", + " -Cell exit\n", + " +getCell(x,y): Cell\n", + " +getNeighbors(cell): List~Cell~\n", + " }\n", + " \n", + " class Cell {\n", + " -int x, y\n", + " -bool isWall\n", + " -bool isStart\n", + " -bool isExit\n", + " +isPassable(): bool\n", + " }\n", + " \n", + " class MazeBuilder {\n", + " <>\n", + " +buildFromFile(filename): Maze\n", + " }\n", + " \n", + " class TextFileMazeBuilder {\n", + " +buildFromFile(filename): Maze\n", + " }\n", + " \n", + " class PathFindingStrategy {\n", + " <>\n", + " +findPath(maze, start, exit): List~Cell~\n", + " }\n", + " \n", + " class BFS\n", + " class DFS\n", + " class AStar\n", + " \n", + " class SearchStats {\n", + " +timeMs: float\n", + " +visitedCells: int\n", + " +pathLength: int\n", + " }\n", + " \n", + " class MazeSolver {\n", + " -Maze maze\n", + " -PathFindingStrategy strategy\n", + " +setStrategy(strategy)\n", + " +solve(): SearchStats\n", + " }\n", + " \n", + " class Observer {\n", + " <>\n", + " +update(event)\n", + " }\n", + " \n", + " class ConsoleView {\n", + " +update(event)\n", + " +render(maze, player, path)\n", + " }\n", + " \n", + " MazeBuilder <|.. TextFileMazeBuilder\n", + " MazeBuilder --> Maze : creates\n", + " PathFindingStrategy <|.. BFS\n", + " PathFindingStrategy <|.. DFS\n", + " PathFindingStrategy <|.. AStarStrategy\n", + " MazeSolver --> PathFindingStrategy : uses\n", + " MazeSolver --> Maze : uses\n", + " Observer <|.. ConsoleView\n", + " MazeSolver --> Observer : notifies\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e268fdf0", + "metadata": {}, + "source": [ + "Builder — позволяет отделить создание сложного объекта от его представления. \n", + "Strategy — позволяет инкапсулировать разные алгоритмы поиска пути так, чтобы их можно было подставлять динамически. \n", + "Observer — позволяет обеспечить реакцию отображения на изменения состояния без жёсткой привязки. \n", + "\n", + "Код можно найти в репозитории: http://31.128.43.79:3000/musinaa/2026-rff_mp" + ] + }, + { + "cell_type": "markdown", + "id": "6ffa70d6", + "metadata": {}, + "source": [ + "# 2. Практическая часть\n", + "# 2.0 Подготовим окружение" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d457dda4", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "sys.path.insert(0, os.path.abspath( '../'))\n", + "\n", + "from task2.mazeBuilder import TextFileMazeBuilder\n", + "from task2.tester import Tester" + ] + }, + { + "cell_type": "markdown", + "id": "888f0e3c", + "metadata": {}, + "source": [ + "## 2.1 Данные для анализа\n", + "\n", + "В папке `mazeExamples` лежит несколько лабиринтов разных размеров: 5x5, 10x10, 50x50, 100x100. Для каждого лабиринта будем искать путь с помощью всех доступных алгоритмов поиска: BFS, DFS и A*. Измерения будем проводить 10 раз, а затем усреднённое значение записывать." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "22ac68eb", + "metadata": {}, + "outputs": [], + "source": [ + "builder = TextFileMazeBuilder()\n", + "tester = Tester(builder, \"docs/data/task2/results.csv\")\n", + "tester.setTestingDirectory(\"task2/mazeExamples\")\n", + "tester.test()\n", + "tester.saveCSV()" + ] + }, + { + "cell_type": "markdown", + "id": "27441b5f", + "metadata": {}, + "source": [ + "## 2.2 Данные по лабиринтам и графики" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "702c1844", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Лабиринт 5x5\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ЛабиринтВремя (мс)Посещённые клеткиДлинна пути
Алгоритм
BFS5x50.07533287
DFS5x50.01840387
AStar5x50.02237187
\n", + "
" + ], + "text/plain": [ + " Лабиринт Время (мс) Посещённые клетки Длинна пути\n", + "Алгоритм \n", + "BFS 5x5 0.075332 8 7\n", + "DFS 5x5 0.018403 8 7\n", + "AStar 5x5 0.022371 8 7" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Лабиринт 10x10\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ЛабиринтВремя (мс)Посещённые клеткиДлинна пути
Алгоритм
BFS10x100.4038145323
DFS10x100.0794383531
AStar10x100.0671462323
\n", + "
" + ], + "text/plain": [ + " Лабиринт Время (мс) Посещённые клетки Длинна пути\n", + "Алгоритм \n", + "BFS 10x10 0.403814 53 23\n", + "DFS 10x10 0.079438 35 31\n", + "AStar 10x10 0.067146 23 23" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Лабиринт 50x50\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ЛабиринтВремя (мс)Посещённые клеткиДлинна пути
Алгоритм
BFS50x503.010086640427
DFS50x502.066407995435
AStar50x502.081632496427
\n", + "
" + ], + "text/plain": [ + " Лабиринт Время (мс) Посещённые клетки Длинна пути\n", + "Алгоритм \n", + "BFS 50x50 3.010086 640 427\n", + "DFS 50x50 2.066407 995 435\n", + "AStar 50x50 2.081632 496 427" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Лабиринт 100x100\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ЛабиринтВремя (мс)Посещённые клеткиДлинна пути
Алгоритм
BFS100x10017.14356824951171
DFS100x1008.43086032191243
AStar100x1004.95179012861171
\n", + "
" + ], + "text/plain": [ + " Лабиринт Время (мс) Посещённые клетки Длинна пути\n", + "Алгоритм \n", + "BFS 100x100 17.143568 2495 1171\n", + "DFS 100x100 8.430860 3219 1243\n", + "AStar 100x100 4.951790 1286 1171" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv(\"data/task2/results.csv\")\n", + "for name in [\"5x5\", \"10x10\", \"50x50\", \"100x100\"]:\n", + " print(f\"\\n Лабиринт {name}\")\n", + " display(df[df[\"Лабиринт\"] == name].set_index(\"Алгоритм\"))" + ] + }, + { + "cell_type": "markdown", + "id": "c7a1b48e", + "metadata": {}, + "source": [ + "Как видно из таблиц, A* рекордно низкое врея выполнения, а так же хорошая длинна пути, часто совпадающая с медленными алгоритмами вроде BFS. Примечательно, что у A* всегда посещает меньше клеток, чем остальные алгоритмы, сильнее всего это заметно на большом лабиринте в 100 клеток, где разрыв от BFS: примерно в 2 раза меньше посещённых клеток, с DFS и вовсе почти в 3 раза.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d5078321", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Лабиринт maze_25x25_wo_exit\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ЛабиринтВремя (мс)Посещённые клеткиДлинна пути
Алгоритм
BFSmaze_25x25_wo_exit1.968229338-1
DFSmaze_25x25_wo_exit0.719102338-1
AStarmaze_25x25_wo_exit1.011797338-1
\n", + "
" + ], + "text/plain": [ + " Лабиринт Время (мс) Посещённые клетки Длинна пути\n", + "Алгоритм \n", + "BFS maze_25x25_wo_exit 1.968229 338 -1\n", + "DFS maze_25x25_wo_exit 0.719102 338 -1\n", + "AStar maze_25x25_wo_exit 1.011797 338 -1" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Лабиринт maze_25x25_empty\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ЛабиринтВремя (мс)Посещённые клеткиДлинна пути
Алгоритм
BFSmaze_25x25_empty4.57453862549
DFSmaze_25x25_empty0.903779625337
AStarmaze_25x25_empty0.2176354949
\n", + "
" + ], + "text/plain": [ + " Лабиринт Время (мс) Посещённые клетки Длинна пути\n", + "Алгоритм \n", + "BFS maze_25x25_empty 4.574538 625 49\n", + "DFS maze_25x25_empty 0.903779 625 337\n", + "AStar maze_25x25_empty 0.217635 49 49" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tester.setTestingDirectory(\"task2/mazeExamplesSpeical\")\n", + "tester.writefile = \"../docs/data/task2/results2.csv\"\n", + "tester.test()\n", + "tester.saveCSV()\n", + "\n", + "df = pd.read_csv(\"data/task2/results2.csv\")\n", + "for name in [\"maze_25x25_wo_exit\", \"maze_25x25_empty\"]:\n", + " print(f\"\\n Лабиринт {name}\")\n", + " display(df[df[\"Лабиринт\"] == name].set_index(\"Алгоритм\"))" + ] + }, + { + "cell_type": "markdown", + "id": "41192153", + "metadata": {}, + "source": [ + "Из выходных данных видно, что A* всегда быстро находит путь в пустом лабиринте, в реальных системах это очень большой плюс. BFS и BFS, из-за своей особенности пытается найти кратчайший путь и ему приходится оббегать весь лабиринт в поисках оптимального пути. \n", + "\n", + "Для того, чтобы оценить различия в алгоритмах на обычных лабиринтах нагляднее, построем серию графиков." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "43409471", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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zvHjx2natClHjx7l008/ZfPmzTz55JO89dZbTJ48Ods+fXx8mD9/vlXZggULKFu2bLa6q1evZvjw4TRu3JiffvqJiIgIwsPD89TfdevWERERwcaNG6lfvz4jRozg+++/z9M+cmJe9sb8Y6ttO3fu5PHHHychIYElS5awevVqvLy86N69O999953N/TZq1Miyz3Xr1j1QG6dOncoLL7xAvXr1+P777/nmm2+4ffs2rVu35vjx4w+0bzODwcDQoUPR6XTZ7uvfvz/ff/8948aNY8eOHURERDBs2LBc7Xfz5s3odDratGlj8/7SpUuzaNEiq7L58+fj6+ubre6SJUt44oknMBqNLFq0iE2bNvHWW29lm4XZpEkTSpUqleMXG0II4QxSUlJ49913qVatGm5ubjz00EMMHTqUW7duZau7atUqWrRoQalSpShVqhSPPPIIS5YssaqzdetW2rdvT+nSpSlZsiStWrXK9kW7rS+qIyMjbX6QbNCgQbYP2w/yPDk9V16/NL7XWq85fSiOjIykR48e+Pj4UKJECRo1apSr9yG29nf9+nWCgoKoW7cucXFxVvVz+lI/6+sYFxfHkCFDCAwMxM3NjWrVqjF58mT0er3V897rx/wB3/ycmb8I379/P2XKlOHZZ5+17NM8eSLz6wnQoUOHXE2GEEIIUXhSUlIAcHd3z9Pj8vqZ9V6fGfPi7NmzDBkyhO+//55169bRq1cvhg0bxpQpU3L1eA8PD6vP8BEREUydOtWqzpAhQ3BxcWHx4sVW5Tdu3GDNmjUMGjSIEiVKsH79ess+zBMHMy95s379+hzbsW/fPpYtW/bAr4cQxY0s1SIcXubTr8B0gJw/fz6dO3fOVtdgMLBlyxZKlCgBQOfOnalatSoffPABW7ZsAWDkyJF4eXmxe/duSpcuDUDHjh1JTU3l448/5q233rIaFH/llVeYO3cun376KeXLlyc5OZmlS5fyyiuvMGPGDKvn//PPP9FqtSxZssQyIz6vp4s1atTI8k3vo48+ytq1azl48CB9+vTJ035sybrsja22jRs3jrJly7Jjxw7Lh/Nu3brxyCOPMHr0aPr06WP1jXtaWho+Pj6WnO43K/teLly4wMSJE3nzzTf57LPPLOUdO3akZs2aTJ48OcfB+7yYP38+Z86coX///ixdutTqvj///JNevXoxdOhQS9nu3btz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jBkzhnbt2uV4HB02bBjNmjWzmYUQQoii9fvvvxMfH8/jjz+e68fk5TNrbj8zgul9R9bPot7e3lYD5xqNBhcXF6tjXXJycraJUwXl6aefpnLlyowaNYqdO3cye/bsB1rWdf/+/SxZsoRNmzbd8wwxIRyZDJwLh3f06FHLKcbx8fGsW7eOLVu28PTTT2ebHa7T6ejYsSMjR47EaDQyffp0EhMTmTx5sqXO3Llzeeyxx2jdujWvv/46VatW5fbt2/zzzz9s2rTJckp4VqNGjaJt27YEBQXds63jxo1j0qRJefoWPLOoqCji4uJIS0sjKiqKLVu2EBoamq3eP//8w/Xr10lNTWXXrl0cPXr0gU/DApg2bRodO3akXbt2jB49Gjc3NxYsWMDRo0dZvXo1Go2GhIQEFi5cyNSpUy2vZUGoWrUqH374IePHj+fMmTN06dKFsmXLcuXKFfbv34+np6dVlnkRERFBiRIl2LRp0z3rTZo0iePHjxMVFXXfU7ezCg0NZenSpZw+fdrqC4asZsyYQf/+/a3OlsisVKlSfPrpp7zyyit06NCBwYMHU6FCBf755x8OHz7M/PnzLXXj4+P5+++/7zlbTgghHFn79u2ZMWMGK1eutDpl+scff+TOnTuWi2x26tQJnU7HwoULadGihc19tWrVijJlynD8+PECOWbmJD/Pk/kL8XvJy5fGWb+0DgkJ4dtvv2X//v3ZBs5r165NzZo1OXz4MFOnTs1Vm20ZO3Ys/v7++Pv7M2zYMIYPH07r1q2tjosJCQls376dJ5988p776tatG7/88gs1atS458z0vJo5cyYuLi5MnjyZ3377jb59+7J///5sgwJr165l27ZtHDx4sMCeWwghRN6lpqby888/89Zbb6HT6ahXrx579+61qpOYmEhycjJ79+6lXr16KIqS58+suf3MmFtPPvkks2bNom/fvrz66qvEx8fzySefWC5qXtB0Oh1Dhw5l7NixeHp65umi3rZ88cUXdO/e/b7HayEcmQycC4f38ssvW257e3tTrVo1Zs2aZbkydGZvvvkmKSkpvPXWW1y9epX69evz888/06pVK0udevXqcejQIcv64VevXqVMmTLUrFnTss65LWXKlLnnkiupqam8+OKLhISEMG7cuHz2FsssL/P66C+99JLND7DmQQF3d3ceeughRowYwZQpU/L9vGZt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brRs2RIAcPXqVRw7dqzU+lyZzWYkJSWVO37a3r17YbPZ8MYbbzjPgLuOOQcAnTp1wpo1ayCEcCbkX375BXq9Hu3atXPOl5ubi+effx5vv/02dDpdmds7cOAA8vLyYDKZnPsTEBCA6OjoKq2nZNyK/jnz1Pbt2zFx4kQMHz4cgGOMttOnT1dpHUREVPuYp5mnAeZpIqKGinmaeRpgnqaGhVecE9WR9u3bY+/evdi0aROOHTuGF154wXkzjCLvvvsuDh8+jNOnT2PlypXYvHlzqa+DAcDAgQMxb948LFiwoNS0Jk2a4Mknn8TMmTOxYsUKJCcnIyEhAe+88w5WrFjhNu+LL76IH374AYcOHcLEiRMRGhpa6o7YRY4ePYoxY8YgKCgId999d5nztGvXDjabDYsXL8bJkyfx6aef4r333nOb569//StOnz6Nv/3tb0hKSsK3336L2bNnY9q0aW5fE/vss8/Qrl27cvsDOL4uN2XKFBw5cgTfffcd5syZg2nTprl9bc2T9dRU+/btER8fj8TERBw4cAAPPfSQ80oFIiJqHJinHZiniYioIWKedmCeJqpbvOKcqI5MnToViYmJGD16NCRJwpgxYxAXF4fvvvvOOc+GDRswZ84cZGdnIyYmBs899xwmT55c5vpmzpxZ7rb+/e9/IywsDK+++ipOnjyJoKAg9OjRA88995zbfPPnz8f06dNx/PhxxMbGYt26ddDr9WWuc+7cubDZbNiyZYvb+GWuunfvjoULF+I///kPnn32WQwYMACvvvqq27hyLVu2xPr16/HMM88gNjYWzZo1w9ixY/Hqq6+6rSs3NxdvvPFGufsIAH/605/QoUMHDBgwAAUFBXjwwQcxd+7cKq+npv773/9i8uTJ6NevH0JDQ/H0008jKyurVrdJRETexTztwDxNREQNEfO0A/M0Ud2ShBCivjtBRHXrp59+wm233YarV68iKCiovrtTLRMnTsS1a9ewdu3a+u4KERGRVzFPExERNVzM00TqwaFaiIiIiIiIiIiIiIhcsHBOREREREREREREROSCQ7UQEREREREREREREbngFedERERERERERERERC5YOCciIiIiIiIiIiIicsHCORERERERERERERGRCxbOiYiIiIiIiIiIiIhcsHBOREREREREREREROSChXMiIiIiIiIiIiIiIhcsnBMRERERERERERERuWDhnIiIiIiIiIiIiIjIBQvnREREREREREREREQu/h8PvnDvwL8cMQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "df = pd.read_csv(\"data/task2/results.csv\")\n", + "\n", + "size_order = [\"5x5\", \"10x10\", \"50x50\", \"100x100\"]\n", + "algo_order = [\"BFS\", \"DFS\", \"AStar\"]\n", + "colors = {\"BFS\": \"blue\", \"DFS\": \"green\", \"AStar\": \"orange\"}\n", + "\n", + "x = range(len(size_order))\n", + "fig, axes = plt.subplots(1, 3, figsize=(15, 4))\n", + "\n", + "params = [\"Время (мс)\", \"Посещённые клетки\", \"Длинна пути\"]\n", + "titles = [\"Время выполнения (мс)\", \"Посещённые клетки\", \"Длина пути\"]\n", + "\n", + "for i, (param, title) in enumerate(zip(params, titles)):\n", + " ax = axes[i]\n", + " for algo in algo_order:\n", + " values = []\n", + " for s in size_order:\n", + " val = df[(df[\"Алгоритм\"] == algo) & (df[\"Лабиринт\"] == s)][param].values\n", + " if len(val) > 0:\n", + " values.append(val[0])\n", + " else:\n", + " values.append(None)\n", + " ax.plot(x, values, marker='o', label=algo, color=colors[algo], linewidth=2, markersize=6)\n", + " ax.set_xticks(x)\n", + " ax.set_xticklabels(size_order)\n", + " ax.set_title(title)\n", + " ax.set_xlabel(\"Размер лабиринта\")\n", + " if i == 0:\n", + " ax.set_ylabel(\"Значение\")\n", + " ax.grid(True, linestyle='--', alpha=0.7)\n", + "\n", + "# Общая легенда сверху\n", + "handles, labels = axes[0].get_legend_handles_labels()\n", + "fig.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5, 1.05), ncol=3)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "c573b32b", + "metadata": {}, + "source": [ + "## 3. Применение паттерна Observer\n", + "\n", + "Для того, чтобы продемонстрировать работу этого паттерна необходимо иметь возможность очищать вывод, но в Jupyter такой возможности нет. В терминале всё работает корректно." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "898e8536", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[H\u001b[2J\u001b[48;5;9m\u001b[38;5;7mS\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;12m\u001b[38;5;7mE\u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "from task2.consoleView import ConsoleView\n", + "from task2.mazeSolver import MazeSolver\n", + "from task2.strategyObjects.AStar import AStar\n", + "\n", + "maze = builder.buildFromFile(\"../task2/mazeExamples/25x25.txt\")\n", + "console = ConsoleView(maze)\n", + "solver = MazeSolver(AStar(), maze)\n", + "solver.attach(console)\n", + "console.render()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "aa5a4c8c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[H\u001b[2J\u001b[48;5;9m\u001b[38;5;7mS\u001b[0m \u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;196m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;161m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\n", + "\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\n", + "\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\n", + "\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\n", + "\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;91m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\n", + "\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + "\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \n", + " \u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;126m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m \u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;56m\u001b[38;5;10m+\u001b[0m\u001b[48;5;7m\u001b[38;5;7m#\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;21m\u001b[38;5;10m+\u001b[0m\u001b[48;5;12m\u001b[38;5;7mE\u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "# При нахождении решения рендер должен произойти автоматически\n", + "_ = solver.solve()" + ] + }, + { + "cell_type": "markdown", + "id": "484b4ef6", + "metadata": {}, + "source": [ + "## 4. Применимость паттернов\n", + "После выполнения работы можно оценить, насколько для этого помогли паттерны ООП. \n", + "Самый полезный паттерн - это конечно Strategy. Он позволил без лишней мороки провести замеры серийно, не используя каждый алгоритм вручную. \n", + " \n", + "Вторым по значимости я бы назвал Observer, он позволил сделать рендер лабиринта проще, однако сильнее всего оценить его получилось бы совместно с паттерном Commad и игроком с передвижениями. Его я не успел реализовать.\n", + " \n", + "Третий - Builder. В этой работе его преимущества оценить трудно из-за того, что использовался только один формат файла - plaintext. Однако, если понадобится искать пути в больших лабиринтах, то их придётся хрвнить в другом формате, например бинарном, и тогда преимщества паттерна Builder станут очевидны. \n", + " \n", + "Так же я создал класс-оркестратор MazeSolver, который помог перенести почти весь код тестов в одно место и удобно эти тесты использовать. Этот паттерн я так же считаю полезным. " + ] + }, + { + "cell_type": "markdown", + "id": "d4026233", + "metadata": {}, + "source": [ + "## 5. Вывод\n", + "Применение объектно-ориентированного подхода и паттернов проектирования позволило разделить ответственность между независимыми модулями и обеспечить простоту внесения изменений. Если бы логика поиска пути, построения лабиринта и визуализации была сосредоточена в одном классе или реализована процедурно, любое расширение требовало бы переписывания значительной части кода." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/MusinAA/docs/data/mazeRezults.csv b/MusinAA/docs/data/mazeRezults.csv deleted file mode 100644 index ad636c75..00000000 --- a/MusinAA/docs/data/mazeRezults.csv +++ /dev/null @@ -1,13 +0,0 @@ -Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути -BFS,50x50,3.2134529003087664,720,431 -BFS,10x10,0.2288821000547614,53,23 -BFS,5x5,0.035640300302475225,8,7 -BFS,100x100,11.366462300065905,2495,1171 -DFS,50x50,1.7320569000730757,747,431 -DFS,10x10,0.0645840002107434,35,31 -DFS,5x5,0.015984299898264,8,7 -DFS,100x100,8.414763500331901,3219,1243 -AStar,50x50,1.8410825001410558,509,455 -AStar,10x10,0.1062644998455653,23,23 -AStar,5x5,0.03181890006089816,8,7 -AStar,100x100,6.592893099877983,1286,1171 diff --git a/MusinAA/docs/data/task2/results.csv b/MusinAA/docs/data/task2/results.csv new file mode 100644 index 00000000..17548aed --- /dev/null +++ b/MusinAA/docs/data/task2/results.csv @@ -0,0 +1,16 @@ +Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути +BFS,10x10,0.4038135000882903,53,23 +BFS,5x5,0.07533170064562,8,7 +BFS,100x100,17.14356810080062,2495,1171 +BFS,50x50,3.010086300491821,640,427 +BFS,25x25,1.0405578999780118,232,173 +DFS,10x10,0.07943829987198114,35,31 +DFS,5x5,0.018403499416308478,8,7 +DFS,100x100,8.430859900545329,3219,1243 +DFS,50x50,2.0664067997131497,995,435 +DFS,25x25,0.5787261994555593,316,173 +AStar,10x10,0.0671462003083434,23,23 +AStar,5x5,0.022370600345311686,8,7 +AStar,100x100,4.951790099585196,1286,1171 +AStar,50x50,2.081632300541969,496,427 +AStar,25x25,0.5791453000711044,186,177 diff --git a/MusinAA/docs/data/task2/results2.csv b/MusinAA/docs/data/task2/results2.csv new file mode 100644 index 00000000..d12deec5 --- /dev/null +++ b/MusinAA/docs/data/task2/results2.csv @@ -0,0 +1,7 @@ +Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути +BFS,maze_25x25_wo_exit,1.9682294001540868,338,-1 +BFS,maze_25x25_empty,4.574537699954817,625,49 +DFS,maze_25x25_wo_exit,0.719102000221028,338,-1 +DFS,maze_25x25_empty,0.903778699648683,625,337 +AStar,maze_25x25_wo_exit,1.0117966015968705,338,-1 +AStar,maze_25x25_empty,0.21763520016975235,49,49 diff --git a/MusinAA/task2/mazeBuilder.py b/MusinAA/task2/mazeBuilder.py index 1fcec597..65ff63ec 100644 --- a/MusinAA/task2/mazeBuilder.py +++ b/MusinAA/task2/mazeBuilder.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from itertools import product import sys +import os.path as path from task2.mazeObjects.maze import Maze from task2.mazeObjects.cell import Cell @@ -18,8 +19,8 @@ class TextFileMazeBuilder(MazeBuilder): задаёт координаты и флаги, после чего возвращает готовый Maze.""" - start = {'x': 0, 'y': 0} - end = {'x': 0, 'y': 0} + start:dict + end:dict def _cellStrategy(self, letter: str) -> Cell: if letter == '#': @@ -63,7 +64,9 @@ class TextFileMazeBuilder(MazeBuilder): cell.y = y array[y][x] = cell except IndexError: - raise ValueError(f"Строка {x+1} имеет длину {len(rows[x])}, ожидалось {width}") + raise ValueError(f"В файле {filename}: Строка {y+1} имеет длину {len(rows[y])}, ожидалось {width}") - return Maze(array, self.start, self.end) + maze_name, _ = path.splitext(path.basename(filename)) + + return Maze(array, self.start, self.end, name=maze_name) \ No newline at end of file diff --git a/MusinAA/task2/mazeExamples/maze_100x100.txt b/MusinAA/task2/mazeExamples/100x100.txt similarity index 100% rename from MusinAA/task2/mazeExamples/maze_100x100.txt rename to MusinAA/task2/mazeExamples/100x100.txt diff --git a/MusinAA/task2/mazeExamples/maze_10x10.txt b/MusinAA/task2/mazeExamples/10x10.txt similarity index 100% rename from MusinAA/task2/mazeExamples/maze_10x10.txt rename to MusinAA/task2/mazeExamples/10x10.txt diff --git a/MusinAA/task2/mazeExamples/25x25.txt b/MusinAA/task2/mazeExamples/25x25.txt new file mode 100644 index 00000000..dbe4af89 --- /dev/null +++ b/MusinAA/task2/mazeExamples/25x25.txt @@ -0,0 +1,25 @@ +S # # # + # ##### # # # ####### # + # # # # # # +#### # ##### # # ####### + # # # # # # # +## # # # ##### ### # # # + # # # # # # # # # + ### # # # # ### # # # # + # # # # # # # # + # ### # # ### ### # #### + # # # # # # # + ### # # # ####### ##### + # # # # + # ################# ### + # # # # + # # # ####### ####### ## + # # # # # + ### ##### # ### ### ### + # # # # # # + # # # ##### # # ####### + # # # # # # + ##### # ####### # ### ## + # # # # # # # +#### # # # ### ##### # # + # # # E diff --git a/MusinAA/task2/mazeExamples/50x50.txt b/MusinAA/task2/mazeExamples/50x50.txt new file mode 100644 index 00000000..bdbcf625 --- /dev/null +++ b/MusinAA/task2/mazeExamples/50x50.txt @@ -0,0 +1,50 @@ +S # # # # # # # + ### # ##### # ### ### # # ### # # ### # ### # # # + # # # # # # # # # # # # # # # + ####### # # ####### ##### # ##### ####### ### ### + # # # # # # # # # # + ### # ##### ### # ### # # ##### # ############# # + # # # # # # # # # # # # # + # ### # # ### ############# # # ##### ##### # # # + # # # # # # # # # # # # # # +#### ##### # ### # ##### # # ##### # ### # ##### # + # # # # # # # # # # # # + ### ####### ####### # ####### # ##### # ### ### # + # # # # # # # # # # # # + ####### # # # ### ##### # ##### ### # ### ####### + # # # # # # # # # # # # # +## ### ####### ### # # ### # # # # ### # ### ### # + # # # # # # # # # # # # + ####### # ##### ####### ### ######### ##### ### # + # # # # # # # # # # # +#### # # ### ##### ####### # # # ### # # # ### # # + # # # # # # # # # # # # # # # # + # # # ### # # # # # # ### ####### # ##### # ### # + # # # # # # # # # # # # # # # # + # # # ### ######### # # ### # # # ##### # # # ### + # # # # # # # # # # # # # + ####### ### # ### ####### # # ##### # ######### # + # # # # # # # # # # # # # + ########### ### ### ### # ### # # ##### # ### # # + # # # # # # # # # # # # # # + ### # ### ### ####### ### # ### # # # ####### # # + # # # # # # # # # # # # # # # # +## # ### # # ### # # # # # # # # # ### # ### ### # + # # # # # # # # # # # # # # # # # # + ##### # # ##### # # # ####### # ### # # # ### # # + # # # # # # # # # # # # # # # + # # # ### # ### ########### # ##### # ### # # ### + # # # # # # # # # # # # # +#### ##### # # ### ### # ##### # ##### # ######### + # # # # # # # # # # # + # ### ##### ### ### # ### ########### ######### # + # # # # # # # # # # + ### # ##### # # ##### # ####### # ### ### # ##### + # # # # # # # # # # # +## ##### # ### ### # ##### ####### # # ######### # + # # # # # # # # # # # # + ### # ##### # # ##### # ### ##### ##### ####### # + # # # # # # # # # # # + # ########### ########### ##### ####### # ### # # + # # # # +################################################ E diff --git a/MusinAA/task2/mazeExamples/low.txt b/MusinAA/task2/mazeExamples/5x5.txt similarity index 100% rename from MusinAA/task2/mazeExamples/low.txt rename to MusinAA/task2/mazeExamples/5x5.txt diff --git a/MusinAA/task2/mazeExamples/maze_50x50.txt b/MusinAA/task2/mazeExamples/maze_50x50.txt deleted file mode 100644 index eea2134c..00000000 --- a/MusinAA/task2/mazeExamples/maze_50x50.txt +++ /dev/null @@ -1,50 +0,0 @@ -S # # # # - ####### ### # # ########### ##### # ##### ##### # - # # # # # # # # # # -## # # ### ####### ##### ####### # ### ######### # - # # # # # # # # # # # # # - ####### # # # # ### ##### # ### ### # # # # ### # - # # # # # # # # # # # # # # # # # - # ### # ### # ### ### # # ### ### ####### # # ### - # # # # # # # # # # # # # # - # # ############# # ### ### # ######### # # ### # - # # # # # # # # # - ########### ########### # ##### ### ### # # # ### - # # # # # # # # # # # # # # - # # ####### # ### # ##### ### ### ### ### # # # # - # # # # # # # # # # # # # # # # -###### ### ### # # # # # # # ### ### ##### # # # # - # # # # # # # # # # # # # # # - ### # # ######### ### # # # # ####### ##### # # # - # # # # # # # # # # # # # # - ### ### # ##### # # ######### # # # # ##### # # # - # # # # # # # # # # # # -## # ######### # # ### ### # ### ######### ##### # - # # # # # # # # # # # # - ##### # ### # ### ##### # # # ####### ##### # # # - # # # # # # # # # # # # # # # -## # ##### # # ##### ##### ### ### # ### # # # ### - # # # # # # # # # # # # # - ##### # ### # # ##### ### # ### ######### # ##### - # # # # # # # # # # # - # ####### ######### ### ####### # # ####### ### # - # # # # # # # # # # # # # # - # # ####### # # ##### # # ### ### # # # # ##### # - # # # # # # # # # # # # # # # # - # ##### # ####### # # # # # ### # ### # # # ### # - # # # # # # # # # # # # # # # - # ########### # ### ####### ### # ### # # # # # # - # # # # # # # # # # # # # - # # ####### ##### ########### ##### # # ##### # # - # # # # # # # # # # # - ### ### ### # ############### # # # ##### ### ### - # # # # # # # # # # # # # # - # ### ### # ### ##### # # # # # ##### # ### # # # - # # # # # # # # # # # # # # - # # ####### # ### ######### ######### ### # # # # - # # # # # # # # # # # # # # # - ##### # ####### # # # ### # # # # # ### ### # # # - # # # # # # # # # # # # # # # -## ### ##### ####### ### # # ### ##### # ### ### # - # # # # -################################################ E diff --git a/MusinAA/task2/mazeExamplesSpeical/maze_25x25_empty.txt b/MusinAA/task2/mazeExamplesSpeical/maze_25x25_empty.txt new file mode 100644 index 00000000..7f7dfce9 --- /dev/null +++ b/MusinAA/task2/mazeExamplesSpeical/maze_25x25_empty.txt @@ -0,0 +1,25 @@ +S + + + + + + + + + + + + + + + + + + + + + + + + E diff --git a/MusinAA/task2/mazeExamplesSpeical/maze_25x25_wo_exit.txt b/MusinAA/task2/mazeExamplesSpeical/maze_25x25_wo_exit.txt new file mode 100644 index 00000000..b977d127 --- /dev/null +++ b/MusinAA/task2/mazeExamplesSpeical/maze_25x25_wo_exit.txt @@ -0,0 +1,25 @@ +S # # # + # ##### # # # ####### # + # # # # # # +#### # ##### # # ####### + # # # # # # # +## # # # ##### ### # # # + # # # # # # # # # + ### # # # # ### # # # # + # # # # # # # # + # ### # # ### ### # #### + # # # # # # # + ### # # # ####### ##### + # # # # + # ################# ### + # # # # + # # # ####### ####### ## + # # # # # + ### ##### # ### ### ### + # # # # # # + # # # ##### # # ####### + # # # # # # + ##### # ####### # ### ## + # # # # # # # +#### # # # ### ##### # # + # # # # diff --git a/MusinAA/task2/mazeObjects/maze.py b/MusinAA/task2/mazeObjects/maze.py index 301d77d0..7ed8fcef 100644 --- a/MusinAA/task2/mazeObjects/maze.py +++ b/MusinAA/task2/mazeObjects/maze.py @@ -7,13 +7,14 @@ class Maze: getCell(x, y), getNeighbors(cell) – возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена).""" - def __init__(self, mazeArray: list[list[Cell]], start: dict, end: dict) -> None: + def __init__(self, mazeArray: list[list[Cell]], start: dict, end: dict, name:str="") -> None: self.mazeArray = mazeArray self.height = len(mazeArray) # X self.width = len(mazeArray[0]) # Y self.startCell = self.getCell(start['x'], start['y']) self.endCell = self.getCell(end['x'], end['y']) + self.name = name def getCell(self, x: int, y: int): return self.mazeArray[y][x] diff --git a/MusinAA/task2/mazeSolver.py b/MusinAA/task2/mazeSolver.py index 6d380e3b..f2e1fc76 100644 --- a/MusinAA/task2/mazeSolver.py +++ b/MusinAA/task2/mazeSolver.py @@ -58,7 +58,7 @@ class MazeSolver(Subject): path = self.strategy.findPath(self._maze, self._maze.startCell, self._maze.endCell) duration = (time.perf_counter() - t_start) * 1000 - path_len = len(path.array) if path.array else 0 + path_len = len(path.array) if path.array else -1 strategy_name = self.getStrategyName() stats = SearchStats(path.array, duration, path.visited_cells, path_len, strategy_name) diff --git a/MusinAA/task2/strategyObjects/AStar.py b/MusinAA/task2/strategyObjects/AStar.py index 43024bd4..6e4c4b2f 100644 --- a/MusinAA/task2/strategyObjects/AStar.py +++ b/MusinAA/task2/strategyObjects/AStar.py @@ -29,7 +29,7 @@ class AStar(PathFindingStrategy): continue visited.add(current) - if current == exit: + if current.isExit: return Path(restorePath(parents, exit), len(visited)) for neighbor in maze.getNeighbors(current): diff --git a/MusinAA/task2/strategyObjects/BFS.py b/MusinAA/task2/strategyObjects/BFS.py index aa84fb94..859897cd 100644 --- a/MusinAA/task2/strategyObjects/BFS.py +++ b/MusinAA/task2/strategyObjects/BFS.py @@ -19,11 +19,14 @@ class BFS(PathFindingStrategy): visited[start] = 0 parents[start] = None + found_exit = False while not q.empty(): current = q.get() # Условие нахождение выхода - if current == exit: break + if current.isExit: + found_exit = True + break # Перебор соседей for hood in maze.getNeighbors(current): @@ -33,4 +36,8 @@ class BFS(PathFindingStrategy): parents[hood] = current q.put(hood) - return Path(restorePath(parents, exit), len(visited)) \ No newline at end of file + if not found_exit: + path_list = None + else: + path_list = restorePath(parents, exit) + return Path(path_list, len(visited)) \ No newline at end of file diff --git a/MusinAA/task2/strategyObjects/DFS.py b/MusinAA/task2/strategyObjects/DFS.py index e72dd009..31d02ebe 100644 --- a/MusinAA/task2/strategyObjects/DFS.py +++ b/MusinAA/task2/strategyObjects/DFS.py @@ -17,11 +17,14 @@ class DFS(PathFindingStrategy): visited[start] = 0 parents[start] = None + found_exit = False while stack: current = stack.pop() # Условие нахождение выхода - if current == exit: break + if current.isExit: + found_exit = True + break # Перебор соседей for hood in maze.getNeighbors(current): @@ -31,4 +34,8 @@ class DFS(PathFindingStrategy): parents[hood] = current stack.append(hood) - return Path(restorePath(parents, exit), len(visited)) \ No newline at end of file + if not found_exit: + path_list = None + else: + path_list = restorePath(parents, exit) + return Path(path_list, len(visited)) \ No newline at end of file diff --git a/MusinAA/task2/tester.py b/MusinAA/task2/tester.py index 1abf6db6..6e56a5f4 100644 --- a/MusinAA/task2/tester.py +++ b/MusinAA/task2/tester.py @@ -18,13 +18,13 @@ class Tester(): лабиринт,стратегия,время_мс,посещено_клеток,длина_пути.""" result:list[SearchStats] def __init__(self, builder:MazeBuilder, writefile:str): - self.builder = builder - self.writefile = writefile + self._builder = builder + self.writefile = "../" + writefile def setTestingDirectory(self, directory:str): if directory[-1] != "/": directory += "/" - self._directory = directory + self._directory = "../" + directory def _getMazes(self) -> list[Maze]: arr = [] @@ -32,7 +32,7 @@ class Tester(): only_txt_files = [f for f in files if os.path.isfile(os.path.join(self._directory, f)) and os.path.splitext(f)[1] == ".txt"] for f in only_txt_files: - arr.append(builder.buildFromFile(os.path.join(self._directory, f))) + arr.append(self._builder.buildFromFile(os.path.join(self._directory, f))) return arr def _solveAvg(self, solver: MazeSolver): @@ -68,15 +68,16 @@ class Tester(): for maze in arr: solver.setMaze(maze) self.result.append(self._solveAvg(solver)) - self.result[-1].maze_name = f"{maze.height}x{maze.width}" + self.result[-1].maze_name = maze.name return self.result if __name__ == "__main__": + exit() from task2.mazeBuilder import TextFileMazeBuilder builder = TextFileMazeBuilder() - tester = Tester(builder, "docs/data/mazeRezults.csv") + tester = Tester(builder, "docs/data/task2/results.csv") tester.setTestingDirectory("task2/mazeExamples") tester.test() tester.saveCSV()