Нежелательное соответствие строк

Я уверен, что здесь отсутствует что-то очевидное, но я не могу заставить R использовать не жадные регулярные выражения:

> library(stringr)
> str_match('xxx aaaab yyy', "a.*?b")                                         
     [,1]   
[1,] "aaaab"

Базовые функции ведут себя одинаково:

> regexpr('a.*?b', 'xxx aaaab yyy')
[1] 5
attr(,"match.length")
[1] 5
attr(,"useBytes")
[1] TRUE

Я ожидаю, что матч будет просто ab в соответствии с "жадным" комментарием в http://stat.ethz.ch/R-manual/R-devel/library/base/html/regex.html:

По умолчанию повторение является жадным, поэтому используется максимально возможное количество повторов.     Это может быть изменено на "минимальное путем добавления? к квантору. (Есть еще     квантификаторы, которые позволяют приблизительное сопоставление: см. документацию TRE.)

Может кто-нибудь, пожалуйста, объясните мне, что происходит?

Обновление. Что сумасшедшее в том, что в некоторых других случаях нежелательные шаблоны ведут себя так, как ожидалось:

> str_match('xxx <a href="abc">link</a> yyy <h1>Header</h1>', '<a.*>')
     [,1]                                          
[1,] "<a href=\"abc\">link</a> yyy <h1>Header</h1>"
> str_match('xxx <a href="abc">link</a> yyy <h1>Header</h1>', '<a.*?>')
     [,1]              
[1,] "<a href=\"abc\">"

Ответы

Ответ 1

Сложная концепция, поэтому я постараюсь изо всех сил... Кто-то может свободно редактировать и объяснять лучше, если он немного запутан.

Выражения, соответствующие вашим шаблонам, выполняются слева направо. Да, все следующие строки aaaab, aaab, aab и ab являются совпадениями с вашим шаблоном, но aaaab является тем, который начинается больше всего слева, и возвращается.

Итак, ваш нежелательный шаблон не очень полезен. Может быть, этот другой пример поможет вам лучше понять, когда не-жадный шаблон ударит:

str_match('xxx aaaab yyy', "a.*?y") 
#      [,1]     
# [1,] "aaaab y"

Здесь все строки aaaab y, aaaab yy, aaaab yyy соответствуют шаблону и начинаются в том же положении, но первый из них был возвращен из-за нежелательного шаблона.


Итак, что вы можете сделать, чтобы поймать последний ab? Используйте это:

str_match('xxx aaaab yyy', ".*(a.*b)")
#      [,1]        [,2]
# [1,] "xxx aaaab" "ab"

Как это работает? Добавив жадный шаблон .* спереди, вы теперь вынуждаете процесс поместить последний возможный a в захваченную группу.

Ответ 2

Проблема соответствует кратчайшему окну между двумя строками. @flodel правильно упоминает, что движок регулярных выражений анализирует строку слева направо, и, следовательно, все совпадения левые. Жадность и лень применимы только к границам справа: жадные кванторы получают подстроки вплоть до самых крайних границ, а ленивые будут соответствовать первому появлению подшаблонов, которые следует соблюдать.

См. примеры :

> library(stringr)
> str_extract('xxx aaaab yyy', "a[^ab]*b")
[1] "ab"
> str_extract('xxx aaa xxx aaa zzz', "xxx.*?zzz")
[1] "xxx aaa xxx aaa zzz"
> str_extract('xxx aaa xxx aaa zzz', "xxx(?:(?!xxx|zzz).)*zzz")
[1] "xxx aaa zzz"

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

Сценарий 1. Границы - это одиночные символы

В случае, если a и b являются одиночными символами, кратчайшее окно можно найти, используя отрицательный класс символов. a[^ab]*b будет легко захватывать подстроку от a до следующего b без a и b между ними.

Сценарий 2. Границы не являются одиночными символами

В этих случаях вы можете использовать умеренный жадный токен, который можно развернуть. Шаблон xxx(?:(?!xxx|zzz).)*zzz соответствует xxx, тогда любые символы 0+, отличные от строкиbreak char, которая не является стартовой char последовательности xxx или zzz char ((?!xxx|zzz) является отрицательный результат, который не соответствует совпадению, если подстрока сразу справа соответствует шаблону lookahead), а затем a zzz.

Эти сценарии сопоставления могут быть легко использованы с базой R regmatches (с использованием регулярного выражения PCRE, поддерживающего lookaheads):

> x <- 'xxx aaa xxx aaa zzz xxx bbb xxx ccc zzz'
> unlist(regmatches(x, gregexpr("xxx(?:(?!xxx|zzz).)*zzz", x, perl = TRUE)))
[1] "xxx aaa zzz" "xxx ccc zzz"

Одно примечание: при использовании регулярного выражения PCRE в базе R или регулярном выражении ICU в str_extract/str_match, . не соответствует символам строки, чтобы включить это поведение, вам нужно добавить (?s) при запуске шаблона (встроенный модификатор DOTALL).