Можем ли мы использовать регулярные выражения для проверки наличия нечетного числа каждого типа символов?
Проблема
Я пытаюсь создать регулярное выражение, в котором мы можем проверить, присутствуют ли все буквы в каком-либо наборе ссылок в некоторой другой строке, но только в нечетных числах (1, 3, 5,...).
Вот (очень) грубое изображение DFA, представляющий проблему:
![Odd As and Bs DFA]()
Мое (разбитое) решение
Я начал использовать конечный набор {a, b}
, поэтому, по существу, проверил бы "есть ли нечетное число a
и нечетное число b
в строке?"
К сожалению, я не ушел слишком далеко. Я впервые прочитал этот поток, который очень похож на эту концепцию, но не смог получить ответ от (aa|bb|(ab|ba)(aa|bb)*(ba|ab))*(b|(ab|ba)(bb|aa)*a)
. (Я понимаю, как это работает, но не как преобразовать его для проверки нечетных чисел для обоих элементов.)
Вот что я до сих пор придумал: ^((ab|ba)(bb|aa)?|(bb|aa)?(ab|ba))+$
. Это в основном проверяет, есть ли ab
или ba
, за которым следует bb
или aa
или ничего, что приведет к ab
, ba
, abaa
, abbb
, baaa
или babb
. (Это также делает обратное этому, сначала проверяя двойную букву.) Это может повториться, неопределенно. У меня проблема: я не могу настроить ее, чтобы она соответствовала строке bbaaba
без соответствия bbaa
.
Кроме того, описанный выше метод не может динамически корректироваться для учета {a, b, c}
, например, хотя я готов отказаться от этого, чтобы решить начальную проблему.
Тестирование
Вот мои тестовые строки и желаемый результат, с указанием причин в скобках:
"ba" # True (1a, 1b)
"abbb" # True (1a, 3b)
"bbba" # True (1a, 3b)
"bbab" # True (1a, 3b)
"ababab" # True (3a, 3b)
"bbaaba" # True (3a, 3b)
"abb" # False (2b)
"aabb" # False (2a, 2b)
"aabba" # False (2b)
"" # False (0a, 0b is "even")
"a" # False (0b is "even")
"b" # False (0a is "even")
Вопрос
Итак, возможно ли это с помощью регулярного выражения? Или регулярные выражения более ограничены, чем DFA? Я знаю, что это можно сделать через базовый цикл, но это не то, что я собираюсь сделать.
Ответы
Ответ 1
Здесь один из способов сделать это, используя lookaheads для утверждения каждого условия в свою очередь.
^(?=[^a]*a(?:[^a]*a[^a]*a)*[^a]*$)(?=[^b]*b(?:[^b]*b[^b]*b)*[^b]*$)(.*)$
Здесь демонстрация с вашими примерами. (\n
в демонстрации предназначены для целей презентации. Также вы можете отказаться от (.*)$
, если вам нужно только проверить совпадение, а не снимать.)
Я скоро добавлю объяснение.
Объяснение
Нам нужно только посмотреть на половину:
(?= [^a]*a (?:[^a]*a[^a]*a) * [^a]*$ )
| | | | |
| | | | Only accept non-'a to the end.
| | | |
| | | Zero or more of these pairs of 'a's.
| | |
| | Strictly a pair of 'a's.
| |
| Find the first 'a'.
|
Use a lookahead to assert multiple conditions.
Ответ 2
Режимы не более ограничены, чем DFA; на самом деле они эквивалентны. ( "Регулярные выражения в стиле Perl" с обратными ссылками являются более сильными, поэтому они не являются "регулярными" вообще.)
Мы можем легко написать регулярное выражение, если строка содержит только a
s:
a(aa)*
И если между ними также могут встречаться другие буквы, мы все равно можем это сделать, просто игнорируя эти символы:
[^a]*a([^a]*a[^a]*a)*[^a]*
Поскольку регулярные выражения эквивалентны DFA, у нас есть DFA для каждой из отдельных букв. Это довольно просто, на самом деле:
[^a] _ [^a] _
/ \ / \
| v a | v
---> (0) -----> ((1))
<-----
a
Состояние (0) - это начальное состояние ( "четное число a
видно" ), а состояние ((1)) является единственным принимающим состоянием ( "нечетное число a
видно" ). Если мы увидим a
, перейдем к другому состоянию; для любого другого символа мы остаемся в том же состоянии.
Теперь хорошая вещь о DFA заключается в том, что они являются составными. В частности, они закрываются при пересечении. Это означает, что если у нас есть DFA, который распознает строку "string", содержащую нечетное число a
s ", а другой, который распознает строку языка, содержащую нечетное число b
s", мы можем объединить их в DFA, который распознает пересечение этих двух языков, то есть "строка, содержащая нечетное число a
и нечетное число b
" s.
Я не буду вдаваться в подробности об алгоритме, но этот вопрос содержит некоторые довольно хорошие ответы. В результате DFA будет иметь четыре состояния: "четное число a
, четное число b
замечено", "четное число a
, нечетное число b
замечено" и т.д.
И поскольку DFA эквивалентны регулярным выражениям, существует также регулярное выражение, которое соответствует именно этим строкам. Опять же, я не буду вдаваться в подробности об алгоритме, но вот статья, которая объясняет это довольно хорошо. Удобно, что он также поставляется с кодом Python 3 для выполнения грязной работы:
>>> from fsm import fsm
>>> a = fsm(
alphabet = {'a', 'b'},
states = {0, 1, 2, 3},
initial = 0,
finals = {3},
map = {
0: {'a': 1, 'b': 2},
1: {'a': 0, 'b': 3},
2: {'a': 3, 'b': 0},
3: {'a': 2, 'b': 1}
}
)
>>> str(a.lego())
'a*(ab|b(ba*b)*(a|ba+b))((a|ba+b)(ba*b)*(a|ba+b)|ba*b)*'
В библиотеке может быть ошибка, или я использую ее неправильно, потому что a*
в начале не может быть прав. Но вы получаете идею: хотя это теоретически возможно, , вы действительно не хотите использовать регулярные выражения для этого!
Ответ 3
Да:
^(?=b*(?:ab*ab*)*ab*$)(?=a*(?:ba*ba*)*ba*$)
Объяснение:
^ # Start of string
(?= # Assert that it possible to match
b* # any number of 'b's
(?:ab*ab*)* # followed by an even number of 'a with optional 'b in-between
ab* # followed by one 'a' and optional 'b's
$ # until the end of the string.
) # End of lookahead
(?=a*(?:ba*ba*)*ba*$) # Same thing, vice versa
Само регулярное выражение не соответствует никаким символам, поэтому вы всегда получите пустую строку в качестве результата соответствия (которая отличается от получения None
в качестве результата сопоставления):
>>> import re
>>> re.match("^(?=b*(?:ab*ab*)*ab*$)(?=a*(?:ba*ba*)*ba*$)", "ab")
<_sre.SRE_Match object at 0x00000000022AA7E8>
>>> re.match("^(?=b*(?:ab*ab*)*ab*$)(?=a*(?:ba*ba*)*ba*$)", "aab")