2013-03-22 6 views
18

здравый смысл и здравомыслие проверка с использованием gregexpr() показывают, что внешний вид-сзади и смотреть вперед утверждения ниже, должны каждый матч ровно в одном месте в testString:Почему strsplit использует положительные выражения lookback и lookbehind по-разному?

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(), чтобы лучше себя вести?


Обновление: отличный ответ Теодора Lytras' объясняет, что происходит, и поэтому адрес (Q1). Мой ответ основывается на его определении средства защиты, обращаясь к (Q2).

+0

FYI , есть несколько смежная дискуссия о том, почему 'stringr :: str_split' ведет себя иначе по отношению к' strsplit' по адресу https://github.com/hadley/stringr/pull/23 – hadley

ответ

21

Я не уверен, является ли это ошибкой, потому что я считаю, что это ожидаемое поведение на основе документации 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. 
} 

Обратите внимание, что это означает, что, если есть совпадение в начале а (непустой) строки, первый элемент вывода - это «» «», но , если в конце строки есть совпадение, результатом является так же, как при удалении совпадения.

Проблема в том, что утверждения 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" 

Что происходит, что одинокие (?=funky) матчей касательно последующих в положении 12. Таким образом, первый раскол включает в себя строку вверх в положении 11 (слева от матча), и он удаляется из строка вместе с совпадением, которая -когда имеет нулевую длину.

Теперь оставшаяся строка funky town, а lookahead соответствует позиции 1.Однако ничего не удалять, потому что ничего не осталось слева от матча, и сам матч имеет нулевую длину. Таким образом, алгоритм застревает в бесконечном цикле. По-видимому, R разрешает это, разбивая один символ, который, кстати, является документированным поведением при strsplit с пустым регулярным выражением (когда аргумент split=""). После этого оставшаяся строка равна unky town, которая возвращается как последний сплит, так как нет совпадения.

Lookbehinds не проблема, потому что каждое совпадение разбивается и удаляется из оставшейся строки, поэтому алгоритм никогда не застревает.

По общему признанию, на первый взгляд это выглядит странно. Однако поведение в противном случае нарушало бы предположение о нулевой длине для наблюдений. Учитывая, что алгоритм strsplit документирован, я считаю, что это не соответствует определению ошибки.

+0

Да, это звучит правильно. Похоже на неудачный способ избежать бесконечного цикла, но похоже, что алгоритм 'strsplit' следующий. Благодаря! –

5

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

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" 

Кажется, работает хорошо, если дано что-то, чтобы захватить вместе с нулевой шириной утверждения, такие as:

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" 

Возможно, что-то подобное может работать как обходной путь.

11

на основе Теодора Lytras' осторожной экспликации substr() «поведения s, разумно чистое решением является префиксом чтобы быть согласованным опережением утверждения с положительным назад 'утверждением, которое соответствует любому одиночному символу:

testString <- "take me to funky town" 
FF2 <- "(?<=.)(?=funky)" 
strsplit(testString, FF2, perl=TRUE) 
# [[1]] 
# [1] "take me to " "funky town" 
+1

Это отличная идея! –

+0

@ TheodoreLytras - Спасибо! Основываясь на ваших объяснениях, я знал, прежде чем попытаться, чтобы это сработало. –