2013-03-22 3 views
18

senso comune e un sanity-check con gregexpr() indicano che il look-behind e look-ahead affermazioni sottostanti devono ogni partita esattamente una posizione nella testString:Perché strsplit usa un lookahead positivo e guarda le corrispondenze di asserzione in modo diverso?

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(), tuttavia, utilizza quei luoghi partita in modo diverso, splitting a una posizione quando si utilizza l'asserzione lookbehind, ma a due posizioni - la seconda delle quali sembra errata - quando si utilizza l'asserzione lookahead.

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

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

Ho due domande: (1T) cosa sta succedendo qui? E (Q2) come si può comportarsi meglio con strsplit()?


Update: risposta eccellente Theodore Lytras' spiega cosa sta succedendo, e così si rivolge (Q1). La mia risposta si basa su di lui per identificare un rimedio, indirizzandosi a (Q2).

+0

FYI , c'è una discussione un po 'correlata sul perché stringr :: str_split' si comporta diversamente da 'strsplit' a https://github.com/hadley/stringr/pull/23 – hadley

risposta

21

Non sono sicuro se questo si qualifica come un bug, perché ritengo che questo sia un comportamento previsto basato sulla documentazione R. Da ?strsplit:

L'algoritmo applicato a ogni stringa di input è

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

noti che questo significa che se esiste una corrispondenza all'inizio di un (non vuota) stringa, il primo elemento dell'output è "" "", ma se c'è una corrispondenza alla fine della stringa, l'output è lo uguale a quello della corrispondenza rimossa.

Il problema è che guardano avanti (e) lookbehind affermazioni sono di lunghezza zero. Così, per esempio, in questo caso:

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" 

Quello che succede è che i solitari guardano avanti (?=funky) partite in posizione 12. Quindi, la prima divisione include la stringa fino alla posizione 11 (a sinistra della partita), e viene rimosso dalla la stringa, insieme con la corrispondenza, che -come sempre- ha lunghezza zero.

Ora la stringa rimanente è funky town e il lookahead corrisponde alla posizione 1.Tuttavia non c'è nulla da rimuovere, perché non c'è niente alla sinistra della partita, e la partita stessa ha lunghezza zero. Quindi l'algoritmo è bloccato in un ciclo infinito. Apparentemente R risolve questo dividendo un singolo carattere, che per inciso è il comportamento documentato quando strsplit con una espressione regolare vuota (quando argomento split=""). Dopodiché la stringa rimanente è unky town, che viene restituita come ultima divisione poiché non c'è corrispondenza.

Lookbehind non sono un problema, perché ogni partita viene divisa e rimossa dalla stringa rimanente, quindi l'algoritmo non è mai bloccato.

Certamente questo comportamento sembra strano a prima vista. Comportarsi diversamente, tuttavia, violerebbe l'assunzione della lunghezza zero per i lookheads. Dato che l'algoritmo strsplit è documentato, credo che questo non soddisfi la definizione di un bug.

+0

Sì, questo suona bene. Sembra un modo sfortunato per evitare il ciclo infinito, ma quello che sembra essere un algoritmo 'strsplit' sta seguendo. Grazie! –

5

Sembra un insetto per me. Questo non sembra essere solo relativi a spazi, in particolare, ma piuttosto qualsiasi lookahead solitaria (positivo o negativo):

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" 

sembra funzionare bene se dato qualcosa per catturare insieme al asserzione a lunghezza zero, come ad come:

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" 

Forse qualcosa del genere potrebbe funzionare come soluzione alternativa.

11

Sulla base di Theodore Lytras' attenta spiegazione del substr() 'comportamento s, una soluzione ragionevolmente pulito è quello di anteporre l'asserzione lookahead to-be-abbinato con un'asserzione lookbehind positivo che corrisponde a qualsiasi singolo carattere:

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

Questa è un'idea eccellente! –

+0

@TheodoreLytras - Grazie! In base alla tua spiegazione, sapevo che prima di provarlo avrebbe funzionato. –