Skip to content

Commit

Permalink
Merge branch 'main' into task-2
Browse files Browse the repository at this point in the history
  • Loading branch information
TreshMom committed Mar 1, 2024
2 parents 2d5d683 + 7c7f67a commit 44029d5
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 9 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: requirements-txt-fixer
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 24.1.1
hooks:
- id: black
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@
- Сторонние пакеты из `requirements.txt` файла
- Английский язык для документации или самодокументирующийся код

## Содержание

* [Из чего складывается оценка за курс](#из-чего-складывается-оценка-за-курс)
* [Летучки](#летучки)
* [Домашние практические работы](#домашние-практические-работы)
* [Работа с проектом](#работа-с-проектом)
* [Домашние практические работы](#домашние-практические-работы-1)
* [Код](#код)
* [Тесты](#тесты)
* [Эксперименты](#эксперименты)
* [Структура репозитория](#структура-репозитория)
* [Контакты](#контакты)
* [Вместо введения](#вместо-введения)

## Из чего складывается оценка за курс

Оценка за курс складывается из баллов, полученных за работу в семестре. Баллы начисляются за следующее.
Expand Down Expand Up @@ -66,7 +80,7 @@
### Домашние практические работы

Работы бывают двух типов:
- С полностью автоматической проверкой. Подразумевается, что к этим задачам известен набор тестов и если он проходит, то задача засчитывается. Количество баллов за такие задачи не менее 60. То есть написав все летучки и сдав все такие задачи можно гарантированно получить 3 (E-D) за курс.
- С полностью автоматической проверкой. Подразумевается, что к этим задачам известны название и сигнатуры функций, а также набор тестов; если тесты проходят, то задача засчитывается. Количество баллов за такие задачи не менее 60. То есть написав все летучки и сдав все такие задачи можно гарантированно получить 3 (E-D) за курс.
- Требующие проверки преподавателем или ассистентом. Как правило, это задачи на постановку экспериментов или разработку относительно нетривиальных решений. Они основаны на задачах предыдущего типа, потому решать их в изоляции затруднительно.

У всех задач есть дедлайн (как правило --- неделя с момента, когда она была задана) после которого максимальный балл за задачу падает в два раза.
Expand Down Expand Up @@ -123,6 +137,22 @@
## Тесты
Тесты бывают двух видов: заготовленные преподавателем и ваши собственные.
Заготовленные тесты существуют в папке `tests/autotests` и используются для проверки задач с полностью автоматической проверкой.
При работе с ними следует соблюдать следующие правила:
- В данных тестах обычно можно изменять только одно --- блок
```python
try:
from project.task2 import regex_to_dfa, graph_to_nfa
except ImportError:
pytestmark = pytest.mark.skip("Task 2 is not ready to test!")
```
В нём необходимо указать из какого(их) модуля(ей) импортировать требуемые функции, в ином случае тесты пропускаются.
- В случае, если вы нашли ошибку **И** готовы её исправить, файл можно изменять, а затем отправлять изменение с помощью Pull Request в основной репозиторий.
- Если же вы нашли ошибку и не готовы заниматься её исправлением, то об этом нужно срочно сообщить преподавателю и не предпринимать других действий!
К вашим собственным тестам применяются следующие правила:
- Тесты для домашних заданий размещайте в папке `tests`.
- Формат именования файлов с тестами `test_[какой модуль\класс\функцию тестирует].py`.
- Для работы с тестами рекомендуется использовать [`pytest`](https://docs.pytest.org/en/6.2.x/).
Expand All @@ -149,6 +179,7 @@
├── scripts - вспомогательные скрипты для автоматизации разработки
├── tasks - файлы с описанием домашних заданий
├── tests - директория для unit-тестов домашних работ
│ └── autotests - директория с автотестами для домашних работ
├── README.md - основная информация о проекте
└── requirements.txt - зависимости для настройки репозитория
```
Expand Down
19 changes: 15 additions & 4 deletions tasks/task2.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
# Задача 2. Построение детерминированного конечного автомата по регулярному выражению и недетерминированного конечного автомата по графу

* **Мягкий дедлайн**: 18.09.2023, 23:59
* **Жёсткий дедлайн**: 21.09.2023, 23:59
* **Жёсткий дедлайн**: 21.02.2024, 23:59
* Полный балл: 5

## Задача

- [ ] Используя возможности [pyformlang](https://pyformlang.readthedocs.io/en/latest/) реализовать **функцию** построения минимального ДКА по заданному регулярному выражению. [Формат регулярного выражения.](https://pyformlang.readthedocs.io/en/latest/usage.html#regular-expression).
- [ ] Используя возможности [pyformlang](https://pyformlang.readthedocs.io/en/latest/) реализовать **функцию** построения минимального ДКА по заданному регулярному выражению. [Формат регулярного выражения](https://pyformlang.readthedocs.io/en/latest/usage.html#regular-expression).
- Требуемая функция:
```python
def regex_to_dfa(regex: str) -> DeterministicFiniteAutomaton:
pass
```
- [ ] Используя возможности [pyformlang](https://pyformlang.readthedocs.io/en/latest/) реализовать **функцию** построения недетерминированного конечного автомата по [графу](https://networkx.org/documentation/stable/reference/classes/multidigraph.html), в том числе по любому из графов, которые можно получить, пользуясь функциональностью, реализованной в [Задаче 1](https://github.com/FormalLanguageConstrainedPathQuerying/formal-lang-course/blob/main/tasks/task1.md) (загруженный из набора данных по имени граф, сгенерированный синтетический граф). Предусмотреть возможность указывать стартовые и финальные вершины. Если они не указаны, то считать все вершины стартовыми и финальными.
- [ ] Добавить необходимые тесты.
- Требуемая функция:
```python
def graph_to_nfa(
graph: MultiDiGraph, start_states: Set[int], final_states: Set[int]
) -> NondeterministicFiniteAutomaton:
pass
```
- [ ] Добавить собственные тесты при необходимости.
4 changes: 2 additions & 2 deletions tasks/task5.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
- [ ] Создать Python notebook, подключить необходимые зависимости.
- [ ] Подключить решения из предыдущих работ.
- [ ] Сформировать набор данных.
- [ ] Выбрать некоторые графы из [набора](https://jetbrains-research.github.io/CFPQ_Data/dataset/index.html). Не забудьте обосновать, почему выбрали именно эти графы.
- [ ] Выбрать некоторые графы из [набора](https://formallanguageconstrainedpathquerying.github.io/CFPQ_Data/graphs/index.html). Не забудьте обосновать, почему выбрали именно эти графы.
- [ ] Используя функцию из первой домашней работы узнать метки рёбер графов и на основе этой информации сформулировать не менее четырёх различных запросов к каждому графу. Лучше использовать наиболее часто встречающиеся метки. Требования к запросам:
- Запросы ко всем графам должны следовать некоторому общему шаблону. Например, если есть графы ```g1``` и ```g2``` с различными наборами меток, то ожидается, что запросы к ним будут выглядеть, например, так:
- ```g1```:
Expand All @@ -49,7 +49,7 @@
- ```(m1 | m3)+ m2*```
- ```m1 m2 m3 (m3|m1)*```
- В запросах должны использоваться все общепринятые конструкции регулярных выражений (замыкание, конкатенация, альтернатива). То есть хотя бы в одном запросе к каждому графу должна быть каждая из этих конструкций.
- [ ] Для генерации множеств стартовых вершин воспользоваться [этой функцией](https://jetbrains-research.github.io/CFPQ_Data/reference/graphs/generated/cfpq_data.graphs.utils.multiple_source_utils.html#cfpq_data.graphs.utils.multiple_source_utils.generate_multiple_source). Не забывайте, что от того, как именно устроено стартовое множество, сильно зависит время вычисления запроса.
- [ ] Для генерации множеств стартовых вершин воспользоваться [этой функцией](https://formallanguageconstrainedpathquerying.github.io/CFPQ_Data/reference/graphs/generated/cfpq_data.graphs.utils.multiple_source_utils.html#cfpq_data.graphs.utils.multiple_source_utils.generate_multiple_source). Не забывайте, что от того, как именно устроено стартовое множество, сильно зависит время вычисления запроса.
- [ ] Сформулировать этапы эксперимента. Что нужно сделать, чтобы ответить на поставленные вопросы? Почему?
- [ ] Провести необходимые эксперименты, замеры
- [ ] Оформить результаты экспериментов
Expand Down
160 changes: 160 additions & 0 deletions tests/autotests/test_task2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# This file contains test cases that you need to pass to get a grade
# You MUST NOT touch anything here except ONE block below
# You CAN modify this file IF AND ONLY IF you have found a bug and are willing to fix it
# Otherwise, please report it
import pyformlang.finite_automaton
from networkx import MultiDiGraph
from pyformlang.regular_expression import Regex
import pytest
import random
import itertools
import networkx as nx

# Fix import statements in try block to run tests
try:
from project.task2 import regex_to_dfa, graph_to_nfa
except ImportError:
pytestmark = pytest.mark.skip("Task 2 is not ready to test!")

REGEX_TO_TEST = [
"(aa)*",
"a | a",
"a* | a",
"(ab) | (ac)",
"(ab) | (abc)",
"(abd) | (abc)",
"(abd*) | (abc*)",
"(abd)* | (abc)*",
"((abd) | (abc))*",
"a*a*",
"a*a*b",
"a* | (a | b)*",
"a*(a | b)*",
"(a | c)*(a | b)*",
]


class TestRegexToDfa:
@pytest.mark.parametrize("regex_str", REGEX_TO_TEST, ids=lambda regex: regex)
def test(self, regex_str: str) -> None:
regex = Regex(regex_str)
regex_cfg = regex.to_cfg()
regex_words = regex_cfg.get_words()

if regex_cfg.is_finite():
all_word_parts = list(regex_words)
word_parts = random.choice(all_word_parts)
else:
index = random.randint(0, 2**9)
word_parts = next(itertools.islice(regex_words, index, None))

word = map(lambda x: x.value, word_parts)

dfa = regex_to_dfa(regex_str)

minimized_dfa = dfa.minimize()
assert dfa.is_deterministic()
assert dfa.is_equivalent_to(minimized_dfa)
assert dfa.accepts(word)


LABELS = ["a", "b", "c", "x", "y", "z", "alpha", "beta", "gamma", "ɛ"]


class GraphWordsHelper:
graph = None
all_paths = None

def __init__(self, graph: MultiDiGraph):
self.graph = graph
self.all_paths = nx.shortest_path(graph)

def is_reachable(self, source, target):
if source not in self.all_paths.keys():
return False
return target in self.all_paths[source].keys()

def _take_a_step(self, node):
for node_to, edge_dict in dict(self.graph[node]).items():
for edge_data in edge_dict.values():
yield {"node_to": node_to, "label": edge_data["label"]}

def _is_final_node(self, node):
return self.graph.nodes(data=True)[node]["is_final"]

def generate_words_by_node(self, node, word=None):
if word is None:
word = list()
for trans in self._take_a_step(node):
tmp = word.copy()
label = trans["label"]
if label != "ɛ":
tmp.append(label)
if self._is_final_node(trans["node_to"]):
yield tmp.copy()
yield from self.generate_words_by_node(trans["node_to"], tmp.copy())

def take_words_by_node(self, node, n):
final_nodes = list(map(lambda x: x[0], self.graph.nodes(data="is_final")))
if any(
map(lambda final_node: self.is_reachable(node, final_node), final_nodes)
):
return itertools.islice(self.generate_words_by_node(node), 0, n)
return []

def get_all_words_less_then_n(self, n: int) -> list[str]:
start_nodes = list(map(lambda x: x[0], self.graph.nodes(data="is_start")))
result = list()
for start in start_nodes:
result.extend(self.take_words_by_node(start, n))
return result


@pytest.fixture(scope="class", params=range(5))
def graph(request) -> MultiDiGraph:
n_of_nodes = random.randint(1, 20)
graph = nx.scale_free_graph(n_of_nodes)

for _, _, data in graph.edges(data=True):
data["label"] = random.choice(LABELS)

return graph


class TestGraphToNfa:
def test_not_specified(self, graph: MultiDiGraph) -> None:
nfa: pyformlang.finite_automaton.NondeterministicFiniteAutomaton = graph_to_nfa(
graph, set(), set()
)
words_helper = GraphWordsHelper(graph)
words = words_helper.get_all_words_less_then_n(random.randint(10, 100))
if len(words) == 0:
assert nfa.is_empty()
else:
word = random.choice(words)
assert nfa.accepts(word)

def test_random(
self,
graph: MultiDiGraph,
) -> None:
start_nodes = set(
random.choices(
list(graph.nodes().keys()), k=random.randint(1, len(graph.nodes))
)
)
final_nodes = set(
random.choices(
list(graph.nodes().keys()), k=random.randint(1, len(graph.nodes))
)
)
nfa: pyformlang.finite_automaton.NondeterministicFiniteAutomaton = graph_to_nfa(
graph, start_nodes, final_nodes
)
words_helper = GraphWordsHelper(graph)
words = words_helper.get_all_words_less_then_n(random.randint(10, 100))
if len(words) == 0:
assert nfa.is_empty()
else:
word = random.choice(words)
assert nfa.accepts(word)

0 comments on commit 44029d5

Please sign in to comment.