Путаница с Atomic Grouping - как она отличается от Grouping в регулярном выражении Ruby?

Я только что просмотрел документы для Atomic Grouping и rubyinfo и некоторые быстрые вопросы пришли мне на ум следующим образом:

  • Почему имя получилось как "Атомная группировка" ? Что "атомарность" , у нее нет общей группировки.
  • Как атомная группировка отличается от общей группировки?
  • Почему атомные группы называются не захватывающими группами?

Я попытался понять код ниже, но имел путаницу в отношении вывода и того, как они работают по одной и той же строке?

irb(main):001:0> /a(?>bc|b)c/ =~ "abbcdabcc"
=> 5
irb(main):004:0> $~
=> #<MatchData "abcc">
irb(main):005:0> /a(bc|b)c/ =~ "abcdabcc"
=> 0
irb(main):006:0> $~
=> #<MatchData "abc" 1:"b">

Ответ 1

A () имеет некоторые свойства (включая такие, как (?!pattern), (?=pattern) и т.д. и простой (pattern)), но общее свойство между ними состоит в группировке, что делает произвольный шаблон a (единица - моя собственная терминология), которая полезна при повторении.

Обычный захват (pattern) имеет свойство захвата и группировки. Захват означает, что текст соответствует шаблону внутри, будет захвачен, чтобы вы могли использовать его с обратной ссылкой в ​​соответствии или замену. Группа, не захватывающая захват (?:pattern), не обладает свойством захвата, поэтому она сохранит немного места и немного ускорится по сравнению с (pattern), поскольку она не хранит индекс начала и конца строки, соответствующей образец внутри.

Атомная группировка (?>pattern) также обладает свойством не захвата, поэтому позиция текста, совпадающего внутри, не будет захвачена.

Атомная группировка добавляет свойство атома по сравнению с группой захвата или не захвата. Атомный здесь означает: в текущей позиции найдите последовательность first (сначала определяется, как движок соответствует в соответствии с заданным шаблоном), который соответствует шаблону внутри атомной группировки и удерживает его (так что откат запрещен).

Группа без атомарности позволит вернуться назад - она ​​все равно найдет первую последовательность, тогда, если совпадение впереди не удастся, оно вернется в исходное состояние и найдет следующую последовательность, пока не будет найдено совпадение для всего выражения регулярного выражения или все возможности исчерпаны.

Пример

Входная строка: bbabbbabbbbc
Выкройка: /(?>.*)c/

Первое совпадение .* составляет bbabbbabbbbc из-за жадного квантификатора *. Он будет соответствовать этому совпадению, запретив c совпадение. Соединитель повторит попытку на следующей позиции до конца строки, и произойдет то же самое. Так что ничего не соответствует регулярному выражению.


Входная строка: bbabbbabbbbc
Вывод: /((?>.*)|b*)[ac]/, для тестирования /(((?>.*))|(b*))[ac]/

С этим регулярным выражением имеется 3 совпадения, которые bba, bbba, bbbbc. Если вы используете второе регулярное выражение, то же самое, но с группами захвата, добавленными для цели отладки, вы можете видеть, что все совпадения являются результатом соответствия b* внутри.

Здесь вы можете увидеть поведение обратного следа.

  • Без атомной группировки /(.*|b*)[ac]/ строка будет иметь одно совпадение, которое представляет собой целую строку, из-за возврата в конец в соответствии с [ac]. Обратите внимание, что движок вернется к .* для возврата на 1 символ, поскольку у него все еще есть другие возможности.

    Pattern: /(.*|b*)[ac]/
    bbabbbabbbbc
    ^             -- Start matching. Look at first item in alternation: .*
    bbabbbabbbbc
                ^ -- First match of .*, due to greedy quantifier
    bbabbbabbbbc
                X -- [ac] cannot match
                  -- Backtrack to ()      
    bbabbbabbbbc
               ^  -- Continue explore other possibility with .*
                  -- Step back 1 character
    bbabbbabbbbc
                ^ -- [ac] matches, end of regex, a match is found
    
  • При атомной группировке все возможности .* обрезаются и ограничиваются первым совпадением. Поэтому после того, как он с жадностью поедает всю строку и не подходит, движок должен идти за шаблоном b*, где он успешно находит совпадение с регулярным выражением.

    Pattern: /((?>.*)|b*)[ac]/
    bbabbbabbbbc
    ^             -- Start matching. Look at first item in alternation: (?>.*)
    bbabbbabbbbc
                ^ -- First match of .*, due to greedy quantifier
                  -- The atomic grouping will disallow .* to be backtracked and rematched
    bbabbbabbbbc
                X -- [ac] cannot match
                  -- Backtrack to ()
                  -- (?>.*) is atomic, check the next possibility by alternation: b*
    bbabbbabbbbc
    ^             -- Starting to rematch with b*
    bbabbbabbbbc
      ^           -- First match with b*, due to greedy quantifier
    bbabbbabbbbc
       ^          -- [ac] matches, end of regex, a match is found
    

    Последующие совпадения будут продолжены здесь.

Ответ 2

"Атомная группа" - это та, где регулярное выражение никогда не будет возвращаться назад. Поэтому в вашем первом примере /a(?>bc|b)c/, если чередование bc в группе совпадает, то оно никогда не отступит от этого и попробуйте чередование b. Если вы слегка измените свой первый пример на соответствие с "abcdabcc", вы увидите, что он по-прежнему совпадает с "abcc" в конце строки, а не с "abc" в начале. Если вы не используете атомную группу, то она может вернуться назад к bc и попробовать чередование b и в конечном итоге соответствовать "abc" в начале.

Что касается второго вопроса, то как он отличается, просто перефразируя ваш первый вопрос.

И, наконец, атомные группы не называются "не-захватывающими" группами. Это не альтернативное имя для них. Нехватывающие группы - это группы, которые не захватывают свой контент. Обычно, когда вы сопоставляете регулярное выражение с строкой, вы можете получить все сопоставленные группы, и если вы используете подстановку, вы можете использовать обратные ссылки в подстановке, например \1, чтобы вставить туда захваченные группы. Но группа, не связанная с захватом, не предоставляет этого. Классическая группа, не захватывающая захват, (?:pattern). Атомная группа также имеет свойство, не связанное с захватом, поэтому она называлась не захватывающей группой.

Ответ 3

Недавно мне пришлось объяснять Atomic Groups кому-то еще, и я подумал, что я подберу и расскажу об этом здесь.

Рассмотрим the (big|small|biggest) (cat|dog|bird).

Соответствует жирным шрифтам

  • большая собака
  • маленькая птица
  • самая большая собака
  • маленькая кошка

Для первой строки механизм regex найдет the. Затем мы переходим к нашим прилагательным (big, small, biggest), он находит big. Сопоставив "большой", он исходит и находит пространство. Затем он смотрит на наших питомцев (cat, dog, bird) и находит cat, пропускает его и находит dog.

Во второй строке наше регулярное выражение найдет the. Он продолжил бы и посмотрел бы на big, пропустил бы его, посмотрел бы и нашел бы small. Затем он находит "". Он смотрит на "кошку", пропускает ее, смотрит на "собаку", пропускает ее и находит "птицу".

В третьей строке наше регулярное выражение найдет the, Он продолжается и находит big, который соответствует немедленному требованию и продолжается. Он не может найти пространство, поэтому он отступает (перематывает позицию до последнего выбора, который он сделал). Он пропускает big, смотрит на small и пропускает его. Он находит самое большое, что также соответствует непосредственному требованию. Затем он находит "". Он смотрит на cat и пропускает его и соответствует dog.

В четвертой строке наше регулярное выражение найдет the. Он перейдет к big, пропустит его, посмотрит и найдет small. Затем он находит "". Он смотрит и соответствует cat.

Теперь рассмотрим the (?>big|small|biggest) (cat|dog|bird) Обратите внимание на атомную группу ?> на прилагательные.

Соответствует жирным шрифтам

  • большая собака
  • маленькая птица
  • самая большая собака
  • маленькая кошка

Для первой строки, второй строки и четвертой строки наш движок работает одинаково.

В третьей строке наше регулярное выражение найдет the, Он продолжается и находит "большой", который соответствует непосредственному требованию и продолжается. Он не может найти пространство, но атомная группа, будучи последним выбором, сделанным двигателем, не позволит повторить этот выбор (запрещает обратное отслеживание). Поскольку он не может сделать новый выбор, совпадение должно потерпеть неудачу, поскольку наше простое выражение не имеет других вариантов.

Это только базовое резюме. Двигателю не нужно было бы смотреть на все cat, чтобы знать, что оно не соответствует dog, достаточно просто посмотреть на c. При попытке сопоставить птицу, c в cat и d на собаке достаточно, чтобы сообщить движку изучить другие параметры.

Однако, если бы у вас было... ((cat|snake)|dog|bird), движок также, конечно же, должен был бы изучить змею, прежде чем она упадет в предыдущую группу, и осмотрит собаку и птицу.

Есть также множество вариантов, которые двигатель не может решить, не пройдя мимо того, что может показаться не совпадением. Если у вас есть ((red)?cat|dog|bird), движок будет смотреть на "r", вернуться, заметить квантор ?, игнорировать подгруппу (red) и искать совпадение.