Почему strsplit использует положительные взгляды, а утверждение lookbehind по-разному?
Здравый смысл и проверка здравомыслия с помощью gregexpr()
показывают, что нижеприведенные утверждения look-behind и look-ahead должны соответствовать одному местоположению в testString
:
testString <- "text XX text"
BB <- "(?<= XX )"
FF <- "(?= XX )"
as.vector(gregexpr(BB, testString, perl=TRUE)[[1]])
# [1] 9
as.vector(gregexpr(FF, testString, perl=TRUE)[[1]][1])
# [1] 5
strsplit()
, однако, использует эти места сопоставления по-разному, разбивая testString
на одно место при использовании утверждения lookbehind, но в двух местах - втором из которых кажется неправильным - при использовании утверждения lookahead.
strsplit(testString, BB, perl=TRUE)
# [[1]]
# [1] "text XX " "text"
strsplit(testString, FF, perl=TRUE)
# [[1]]
# [1] "text" " " "XX text"
У меня есть два вопроса: (Q1) Что здесь происходит? И (Q2), как можно лучше strsplit()
вести себя?
Обновление: Отличный ответ Теодора Литраса объясняет, что происходит, и поэтому адресует (Q1). Мой ответ основывается на нем, чтобы определить средство, адресованное (Q2).
Ответы
Ответ 1
Я не уверен, относится ли это к ошибке, потому что я считаю, что это ожидаемое поведение на основе документации R. От ?strsplit
:
Алгоритм, применяемый к каждой входной строке,
repeat {
if the string is empty
break.
if there is a match
add the string to the left of the match to the output.
remove the match and all to the left of it.
else
add the string to the output.
break.
}
Обратите внимание, что это означает, что если есть совпадение в начале a (непустая) строка, первый элемент вывода - "", но если в конце строки есть совпадение, то результатом является как и при удалении совпадения.
Проблема заключается в том, что утверждения lookahead (и lookbehind) имеют нулевую длину. Так, например, в этом случае:
FF <- "(?=funky)"
testString <- "take me to funky town"
gregexpr(FF,testString,perl=TRUE)
# [[1]]
# [1] 12
# attr(,"match.length")
# [1] 0
# attr(,"useBytes")
# [1] TRUE
strsplit(testString,FF,perl=TRUE)
# [[1]]
# [1] "take me to " "f" "unky town"
Что происходит, так это то, что одиночный lookahead (?=funky)
соответствует в позиции 12. Таким образом, первый split включает строку до позиции 11 (слева от совпадения), и она удаляется из строки вместе с совпадением, что - все равно - имеет нулевую длину.
Теперь оставшаяся строка funky town
, а lookahead соответствует позиции 1. Однако там ничего не удалить, потому что ничего не осталось слева от совпадения, а совпадение имеет нулевую длину. Таким образом, алгоритм застревает в бесконечном цикле. Очевидно, R разрешает это, разбивая один символ, который, кстати, является документированным поведением при strsplit
ing с пустым регулярным выражением (когда аргумент split=""
). После этого оставшаяся строка unky town
, которая возвращается как последний раскол, так как нет совпадения.
Lookbehinds не проблема, потому что каждое совпадение разбивается и удаляется из оставшейся строки, поэтому алгоритм никогда не застревает.
По общему признанию, на первый взгляд это выглядит странно. Однако поведение в противном случае нарушало бы предположение о нулевой длине для наблюдений. Учитывая, что алгоритм strsplit
документирован, я считаю, что это не соответствует определению ошибки.
Ответ 2
Основываясь на тщательном объяснении поведения substr()
Теодора Литраса, разумное чистое обходное решение - это префикс предполагаемого утверждения lookahead с положительным утверждением lookbehind, которое соответствует любому одиночному символу:
testString <- "take me to funky town"
FF2 <- "(?<=.)(?=funky)"
strsplit(testString, FF2, perl=TRUE)
# [[1]]
# [1] "take me to " "funky town"
Ответ 3
Похож на ошибку. Это, по-видимому, не просто связано с пробелами, а скорее с любым одиноким взглядом (положительным или отрицательным):
FF <- "(?=funky)"
testString <- "take me to funky town"
strsplit(testString,FF,perl=TRUE)
# [[1]]
# [1] "take me to " "f" "unky town"
FF <- "(?=funky)"
testString <- "funky take me to funky funky town"
strsplit(testString,FF,perl=TRUE)
# [[1]]
# [1] "f" "unky take me to " "f" "unky "
# [5] "f" "unky town"
FF <- "(?!y)"
testString <- "xxxyxxxxxxx"
strsplit(testString,FF,perl=TRUE)
# [[1]]
# [1] "xxx" "y" "xxxxxxx"
Кажется, что он отлично работает, если ему дано что-то захватить вместе с утверждением нулевой ширины, например:
FF <- " (?=XX )"
testString <- "text XX text"
strsplit(testString,FF,perl=TRUE)
# [[1]]
# [1] "text" "XX text"
FF <- "(?= XX ) "
testString <- "text XX text"
strsplit(testString,FF,perl=TRUE)
# [[1]]
# [1] "text" "XX text"
Возможно, что-то подобное может работать как обходное решение.