Skip to content

Лабораторная работа 1. Тверитнев Михаил#188

Open
ktveritnev23 wants to merge 2 commits intomainfrom
tveritnev
Open

Лабораторная работа 1. Тверитнев Михаил#188
ktveritnev23 wants to merge 2 commits intomainfrom
tveritnev

Conversation

@ktveritnev23
Copy link
Collaborator

Вопросы по возможностям современных regex-машин в современных ЯП и конечным автоматам.

4. Поддержка захватывающих (possessive) квантификаторов и атомарных группировок.
5. НКА поддерживает обработку многоопределённости.
В то же время, ДКА имеет полиномиальное время выполнения, в отличие от экспоненциального для НКА.
Комбинирование ДКА и НКА позволяет достигать необходимого баланса между скоростью работы и занимаемым в памяти местом (как известно, НКА с n состояниями может соответствовать ДКА с 2^n состояниями, а НКА имеет в худшем случае экспоненциальное время выполнения). При этом большое количество состояний и объём занимаемой ими памяти также могут негативно влиять на общую производительность (в случае непопадания в кэш процессорному ядру приходится обращаться в оперативную память, что увеличивает задержки).
Copy link
Owner

@BaldiSlayer BaldiSlayer Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

в случае непопадания в кэш процессорному ядру приходится обращаться в оперативную память, что увеличивает задержки

подушню, но L3 кеш например пошарен между ядрами, но не думаю что тут оч сильно это важно

но формулировка ядро идет в оперативку мне как-то не оч нравится если честно

questions:
- 'Что такое "сильная" LL(k) грамматика и чем она отличается от обычной?
'
- answer: 'Обычно переписывание регулярных выражений для их ускорения осуществляется вручную, поскольку общего алгоритма для их оптимизации пока не существует, а доступные способы оптимизации зависят от сценария использования регуляного выражения (например, a{min, max} быстрее (a+)+, но для такой замены необходимо знать, сколько повторяющихся символов может быть во входной строке). Однако есть библиотеки и приложения, позволяющие оптимизировать регулярки хотя бы частично. Основной метод оптимизации - так называемая факторизация альтернаций суффиксов и префиксов, при которой альтернации максимально сокращаются, чтобы минимизировать обратные переходы (англ. backtracking). Например, `cat|car|calf` заменяется на `ca(?:t|r|lf)`, а `foo|goo` - на `[fg]oo`. Такой оптимизатор можно найти в языке Perl (модуль Regexp::Optimizer).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

где-то тут кажется сломан формат yaml

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Регулярка (a+)+ в принципе ужасна )) Почему для её оптимизации нельзя просто заменить её хотя бы на a*(a+) (если вдруг нужна группа захвата) или тупо на a+, если группа захвата не нужна, а понадобились ограничители, мне не очень понятно.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Регулярка (a+)+ в принципе ужасна )) Почему для её оптимизации нельзя просто заменить её хотя бы на a*(a+) (если вдруг нужна группа захвата) или тупо на a+, если группа захвата не нужна, а понадобились ограничители, мне не очень понятно.

язык (C+)+

))

Современные устройства позволяют обрабатывать данные в несколько потоков (если учитывать возможность вычислений на GPU, то доступны сотни потоков).
Существует два основных подхода к параллелизации регулярных выражений:
1. Построение параллельного конечного автомата (англ. parallel finite-state machine, PFSM) для регулярного выражения.
2. Разбиение данных на несколько частей, каждая из которых обрабатывается движком регулярных выражений в отдельном потоке. Например, в Java для управлении потоками используется библиотека `java.util.concurrent`, в C++ - `std::thread`, а в Go - `goroutines`.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLDR
ну строго говоря горутина != поток) и надо чуть пошаманить, чтобы горутины были на разных логических ядрах

еще подушню, но горутины и std::thread это разное с точки зрения того, что это под капотом. std:thread является потоком ОС, а вот горутина им не является, горутины шедулятся рантаймом Go, а потоки ОС планировщиком ОС

и тд)
можно долго продолжать, но прям сильно душить уж не буду

скажу только то, что горутина это абстракция над потоком ОС (будет интересно - прочитай про Green threads)

author: Тверитнев Михаил
id: 266
questions:
- 'Обратные ссылки с заданной глубиной в интерпретаторе регулярных выражений языка Ruby.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а такое ток в Ruby есть?

- 'Простой способ построения пересечения регулярных языков с помощью lookahead
'
- answer: 'Алгоритм Бржозовского (Brzozowski algorithm) позволяет минимизировать любой конечный автомат, даже недетерминированный, посредством последовательного двукратного применения к нему операций обращения и детерминизации.
Теорема Бржозовского (англ. Brzozowski theorem, 1962): Пусть A - конечный автомат, распознающий язык L, det(A) - детерминизированный автомат, rev(A) - обратный автомат для А (т.е. такой автомат, в котором начальные и конечные состояния поменяли местами, а направления всех переходов изменены на обратные). Минимальный ДКА для автомата А может быть построен следующим образом: Amin = det(rev(det(rev(A)))).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1962 нам если честно не очень важно

S -> aAa
S -> bA2ba
A -> b | e
A2 -> b | e.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай A_2 заменим условно на C ) не вижу смысла юзать нижний регистр там, где можно обойтись без него

Copy link

@TonitaN TonitaN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Материал интересный и нетривиальный, хотя иногда очень специфический, но понравилось.

5. НКА поддерживает обработку многоопределённости.
В то же время, ДКА имеет полиномиальное время выполнения, в отличие от экспоненциального для НКА.
Комбинирование ДКА и НКА позволяет достигать необходимого баланса между скоростью работы и занимаемым в памяти местом (как известно, НКА с n состояниями может соответствовать ДКА с 2^n состояниями, а НКА имеет в худшем случае экспоненциальное время выполнения). При этом большое количество состояний и объём занимаемой ими памяти также могут негативно влиять на общую производительность (в случае непопадания в кэш процессорному ядру приходится обращаться в оперативную память, что увеличивает задержки).
Что касается реализаций regex-машин в языках программирования, большинство современных regex-машин (PHP, PCRE2, Python re) используют НКА для поддержки своего расширенного синтаксиса. Regex-движки, реализующие ДКА, также используются в наше время - например, в реализации регулярок в Microsoft SQL Server (использование: <string_name> LIKE <regex>). Существуют и гибридные реализации, примерами которых являются re2 в Golang и библиотека Hyperscan от Intel.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот тут важно отметить, что перечисленные вами НКА машины вовсе не регулярные языки описывают. Они используют не НКА, а Extended NFA - расширенные НКА, у которых от НКА только каркас, а операции на ребрах могут быть и нерегулярными.
И re2 всё-таки не гошная библиотека изначально, а плюсовая. Другой вопрос, что гошные стандартные regexp имеют re2-совместимый синтаксис, и собственно re2, насколько помню, имеет гошный API. Поэтому я бы разделила здесь гошные regexp (тоже гибридные) и re2 выражения.

questions:
- 'Какие regex-машины используют в своей реализации и ДКА, и НКА?
'
- answer: 'Сильная LL(k)-грамматика (англ. strong LL(k) grammar) -- это LL(k)-грамматика, у которой, если в грамматике существуют правила A -> α, B -> β для всех нетерминалов множества FIRSTk(α FOLLOWk(A)) и FIRSTk(β FOLLOWk(A)) не имеют пересечений. На практике это означает, что решение разбора принимается только на основе следующих k токенов (символов). Это и есть определение LL(k)-грамматики, что позволяет сделать вывод о том, что любой LL(k) язык возможно описать LL(k)-сильной грамматикой. Однако не все LL(k)-грамматики являются сильными. Пример грамматики, не являющейся LL(k)-сильной (k = 2):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A и B никак не связаны, и B не используется нигде, кроме как в правиле. Поэтому сложно понять, что это за условие.

questions:
- 'Что такое "сильная" LL(k) грамматика и чем она отличается от обычной?
'
- answer: 'Обычно переписывание регулярных выражений для их ускорения осуществляется вручную, поскольку общего алгоритма для их оптимизации пока не существует, а доступные способы оптимизации зависят от сценария использования регуляного выражения (например, a{min, max} быстрее (a+)+, но для такой замены необходимо знать, сколько повторяющихся символов может быть во входной строке). Однако есть библиотеки и приложения, позволяющие оптимизировать регулярки хотя бы частично. Основной метод оптимизации - так называемая факторизация альтернаций суффиксов и префиксов, при которой альтернации максимально сокращаются, чтобы минимизировать обратные переходы (англ. backtracking). Например, `cat|car|calf` заменяется на `ca(?:t|r|lf)`, а `foo|goo` - на `[fg]oo`. Такой оптимизатор можно найти в языке Perl (модуль Regexp::Optimizer).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Регулярка (a+)+ в принципе ужасна )) Почему для её оптимизации нельзя просто заменить её хотя бы на a*(a+) (если вдруг нужна группа захвата) или тупо на a+, если группа захвата не нужна, а понадобились ограничители, мне не очень понятно.

questions:
- 'Существуют ли инструменты для автоматической оптимизации регулярных выражений и как они устроены?
'
- answer: 'Regex-движок Ruby Onigmo обладает уникальной особенностью: он позволяет использовать обратные ссылки с обозначением относительной глубины рекурсии, на которой должна быть ссылаемая группа. Например, выражение `\b(?word(?letter[a-z])\gword\kletter+0|[a-z])\b` распознаёт только палиндромы нечётной длины (например, слова `radar`, `racecar`, но не `abba` или `aa`). Если модифицировать данный пример, адресуя группу на предыдущем уровне рекурсии (`\b(?word(?letter[a-z])\gword(?:\kletter-1|z)|[a-z])\b`), можно распознавать такие слова, как `radrb`, `racearz`. Назовём их `сжатыми палиндромами`, где правая половина сдвинута к первой с потерей левых символов. Если же применить обратную ссылку с адресацией на следующем уровне рекурсии (`\b(?word(?letter[a-z])\gword(?:\kletter+1|z)|[a-z])\b`), мы получим выражение, распознающее слова вида `radzr`, `racezca`. Их можно назвать `растянутыми палиндромами`, где правая половина сдвигается вправо с потерей правых символов.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот эти выражения прямо пошагово расшифровать надо. В частности, разницу между ссылками \gName и \kName.

PS - Нигде кроме Oniguruma таких изощренностей, как сдвиг рекурсии, не видела. Не очень понимаю, как это осознанно использовать, но факт забавный.

- 'Обратные ссылки с заданной глубиной в интерпретаторе регулярных выражений языка Ruby.
'
- answer: 'В общем случае получение регулярного выражения, распознающего пересечение двух регулярных языков, довольно сложно. Для этого нужно представить каждый регулярный язык в виде ДКА, построить их прямое произведение и затем преобразовать результат в регулярное выражение.
Однако в современных regex-машинах, таких, как PCRE2 в PHP или V8 в JavaScript, есть возможность использовать опережающие проверки (lookahead) для получения пересечения. Например, если есть regex1 и regex2 для языков L1 и L2 соответственно, то PCRE2-совместимое регулярное выражение, распознающее пересечение этих языков, можно построить следующим образом: `^(?=regex1$)(?=regex2$)`. Следует помнить, что такой подход обладает своими недостатками:
Copy link

@TonitaN TonitaN Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

И по итогу оно будет вычислять только одну подходящую подстроку. Т.к. после lookahead ещё нужно добавить основную часть, если мы пытаемся всё выражение распарсить как пересечение.

Тут кмк проще обойтись одной опережающей проверкой, а вторую регулярку положить как раз в базу.

'
Современные устройства позволяют обрабатывать данные в несколько потоков (если учитывать возможность вычислений на GPU, то доступны сотни потоков).
Существует два основных подхода к параллелизации регулярных выражений:
1. Построение параллельного конечного автомата (англ. parallel finite-state machine, PFSM) для регулярного выражения.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На этот случай в регулярки вводится замечательная операция асинхронной композиции или шаффла, #. Её замечательность - в том, что она коммутативна, и алгебра регулярок с такой операцией позволяет определить производную произведения (асинхронной композиции) так же, как она определяется в классическом матанализе. Рекомендую в этом ответе добавить определение шаффла, раз уж пошла такая история.

data/data.yaml Outdated
'
- answer: 'В PCRE2-совместимых регулярных выражениях (полное название стандарта - Perl Compatible Regular Expressions) предусмотрено использование нескольких способов обращения к скобочным группам, или группам захвата (англ. captive group):
1. Прямое обращение по номеру. Группы в регулярках нумеруются слева направо, начиная с 1.
2. Именованные группы. Для задания имени группе захвата используется следующий синтаксис: `(?<name>)`, где <name> - имя группы. Примеры:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут не хватает примеров

Пример: `^(?<Counter>A)+ (?<-Counter>B)+ (?(Counter)(?!))$` распознаёт слова из языка L = {A^nB^n | n ∈ N, n > 0 }.
Более того, со стека можно снимать несколько захваченных символов. Например, регулярное выражение `^(?=.*[a-z](?<N>)|)(?=.*[A-Z](?<N>)|)(?=.*[0-9](?<N>)|)(?<-N>){2}.{8,}` распознаёт слова (пароли), состоящие не менее чем из восьми символов и содержащие не менее двух из трёх видов символов: строчные буквы, прописные буквы, цифры.
'
author: Тверитнев Михаил
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вопрос отрезался.

@TonitaN
Copy link

TonitaN commented Jan 21, 2025

На всякий случай, чтобы не было "никаких разночтений (с)" - когда я предлагаю правки, их совсем не обязательно и обычно не желательно вставлять в ответ методом copy&paste (исключение - если речь о точных формулировках). Всегда лучше подстраивать предложение под контекст. Да и некрасиво, если стилистика ответа начинает резко меняться, потому что часть писал студент, а часть - получается что я. Не к тому, что вы так делали, а к тому, что так делали некоторые другие, и на этот образец равняться не надо.

Тегну здесь ещё @ark2016 с аналогичным пожеланием, чтобы вам не казалось, что про вас забыли)

1) Добавлен обрезанный вопрос с id=274;
2) Добавлены примеры для вопроса с id=273
@TonitaN
Copy link

TonitaN commented Feb 28, 2025

@ktveritnev23 , по всем правкам нужно пройтись, и не в последнюю ночь перед комиссией

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants