¿Por qué strsplit usa un look-up positivo y una afirmación look-behind de manera diferente?

El sentido común y una comprobación de cordura con gregexpr() indican que las afirmaciones de mirar hacia atrás y mirar hacia adelante a continuación deben coincidir exactamente en una ubicación en 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() , sin embargo, usa esas ubicaciones de coincidencia de manera diferente, dividiendo testString en una ubicación cuando se usa la aserción lookbehind, pero en dos ubicaciones, la segunda de las cuales parece incorrecta, al usar la aserción de búsqueda anticipada.

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

Tengo dos preguntas: (P1) ¿Qué está pasando aquí? Y (P2) ¿cómo se puede lograr que strsplit() se comporte mejor?


Actualización: la excelente respuesta de Theodore Lytras explica lo que está sucediendo, y por lo tanto aborda (Q1) . Mi respuesta se basa en la suya para identificar un remedio, abordar (Q2) .

No estoy seguro de si esto califica como un error, porque creo que se espera que este comportamiento se base en la documentación de R. Desde ?strsplit :

El algoritmo aplicado a cada cadena de entrada es

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

Tenga en cuenta que esto significa que si hay una coincidencia al comienzo de una cadena (no vacía), el primer elemento de la salida es ” ” ”, pero si hay una coincidencia al final de la cadena, la salida es lo mismo que con el partido eliminado.

El problema es que las aserciones de anticipación (y posterior) son de longitud cero. Entonces, por ejemplo, en este 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" 

Lo que sucede es que el simple lookahead (?=funky) coincide en la posición 12. Entonces, la primera división incluye la secuencia hasta la posición 11 (a la izquierda del partido), y se elimina de la cadena, junto con la coincidencia, que - sin embargo, tiene longitud cero.

Ahora el rest de la cadena es funky town , y el lookahead coincide en la posición 1. Sin embargo, no hay nada que eliminar, porque no hay nada a la izquierda del partido, y el partido en sí tiene una longitud cero. Entonces el algoritmo está atrapado en un bucle infinito. Aparentemente, R resuelve esto dividiendo un solo carácter, que incidentalmente es el comportamiento documentado cuando se strsplit con una expresión regular vacía (cuando el argumento se split="" ). Después de esto, la cadena restante es unky town , que se devuelve como la última división ya que no hay coincidencia.

Mirar hacia atrás no es un problema, porque cada partida se divide y elimina de la cadena restante, por lo que el algoritmo nunca se bloquea.

Es cierto que este comportamiento se ve raro a primera vista. Comportarse de otra manera, sin embargo, violaría la suposición de longitud cero para lookaheads. Dado que el algoritmo strsplit está documentado, creo que esto no cumple con la definición de error.

En función de la cuidadosa explicación de Theodore Lytras sobre el comportamiento de substr() , una solución razonablemente limpia consiste en agregar como prefijo la afirmación de búsqueda anticipada compatible con una aseveración de búsqueda positiva positiva que coincida con cualquier carácter individual:

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

Parece un error para mí. Esto no parece estar relacionado solo con espacios, específicamente, sino más bien con cualquier anticipación solitaria (positiva o negativa):

 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" 

Parece funcionar bien si se le da algo para capturar junto con la aserción de ancho cero, como:

 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" 

Quizás algo así podría funcionar como una solución alternativa.