2013-03-22 17 views
18

Rozsądek i poczytalność-check użyciu gregexpr() wskazują, że wygląd opóźnieniem i antycypowana twierdzeń poniżej powinny każdy mecz na dokładnie jeden lokalizacja w testString:Dlaczego funkcja strsplit używa pozytywnej asysty z wyprzedzeniem i asymilacji lookbehind w inny sposób?

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() jednak wykorzystuje te lokalizacje meczu inaczej, podział testString na jedno miejsce podczas korzystania z asercji lookbehind, ale w przypadku dwóch lokalizacji - z których druga wydaje się niepoprawna - podczas korzystania z asercji przewidującej.

strsplit(testString, BB, perl=TRUE) 
# [[1]] 
# [1] "text XX " "text"  

strsplit(testString, FF, perl=TRUE) 
# [[1]] 
# [1] "text" " "  "XX text" 

Mam dwa pytania: (Q1) Co tu się dzieje? I (Q2) jak można uzyskać strsplit(), aby lepiej się zachowywać?


Aktualizacja: doskonała odpowiedź Theodore Lytras wyjaśnia, co się dzieje, a więc odnosi (Q1). Moja odpowiedź opiera się na jego identyfikacji środka zaradczego, adresując: (Q2).

+0

FYI , jest trochę związana dyskusja o tym, dlaczego 'stringr :: str_split' zachowuje się inaczej niż' strsplit' na https://github.com/hadley/stringr/pull/23 – hadley

Odpowiedz

21

Nie jestem pewien, czy kwalifikuje się to jako błąd, ponieważ uważam, że jest to oczekiwane zachowanie oparte na dokumentacji R. Od ?strsplit:

Algorytm stosowany do każdego ciągu wejściowego jest

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. 
} 

Zauważ, że oznacza to, że jeśli jest mecz na początku a (niepusty) strun, pierwszy element wyjście ma wartość "" ", ale , jeśli na końcu łańcucha występuje dopasowanie, wynikiem jest , podobnie jak w przypadku dopasowania.

Problem polega na tym, że twierdzenia wyprzedzające (i lookbehind) mają zerową długość. Tak na przykład w tym przypadku:

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" 

Co się dzieje, że samotne uprzedzona (?=funky) mecze w pozycji 12. Tak więc pierwszy rozłam zawiera ciąg aż do pozycji 11 (lewa meczu) i jest usuwany z ciąg wraz z dopasowaniem, który ma jednak zerową długość.

Pozostały ciąg to funky town, a poprzedni pasuje do pozycji 1.Nie ma jednak nic do usunięcia, ponieważ po lewej stronie meczu nic nie ma, a samo dopasowanie ma zerową długość. Tak więc algorytm utknął w nieskończonej pętli. Wygląda na to, że R rozwiązuje to przez podział pojedynczego znaku, co jest przypadkiem udokumentowanym zachowaniem przy pustym wyrażeniu regularnym (gdy argument split=""). Następnie pozostały łańcuch to unky town, który jest zwracany jako ostatni podział, ponieważ nie ma dopasowania.

Obiekty Lookbehinds nie stanowią problemu, ponieważ każdy mecz jest dzielony i usuwany z pozostałego łańcucha, dzięki czemu algorytm nie zostaje zablokowany.

To zachowanie na pierwszy rzut oka wygląda dziwnie. Zachowanie się w inny sposób naruszyłoby jednak założenie zerowej długości dla uprzedzeń. Biorąc pod uwagę, że algorytm strsplit jest udokumentowany, uważam, że nie spełnia on definicji błędu.

+0

Tak, to brzmi jak trzeba. Wydaje się niefortunnym sposobem na uniknięcie nieskończonej pętli, ale wygląda na to, że algorytm 'strsplit' jest następujący. Dzięki! –

5

Wygląda jak błąd. To nie wydaje się być po prostu związane z przestrzeni, w szczególności, ale raczej każdy samotny uprzedzona (dodatni lub ujemny):

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" 

wydaje się działać prawidłowo, jeśli dana coś uchwycić wraz z zerowej szerokości twierdzenia, takie jako:

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" 

Być może coś takiego może funkcjonować jako obejście tego problemu.

11

podstawie Theodore Lytras' starannego eksplikacji substr() „zachowanie s, rozsądnie czyste obejście jest prefiks do-dopasowaną uprzedzoną twierdzenie z pozytywnym stwierdzeniem lookbehind, który pasuje do każdego pojedynczego znaku:

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

To doskonały pomysł! –

+0

@TheodoreLytras - Dzięki! Na podstawie twoich wyjaśnień, wiedziałem, zanim spróbowałem, że to zadziała. –