【问题标题】:Why does strsplit use positive lookahead and lookbehind assertion matches differently?为什么 strsplit 使用正向前瞻和后向断言匹配不同?
【发布时间】:2013-03-12 14:20:35
【问题描述】:

常识和使用gregexpr() 进行的健全性检查表明,下面的后视和前瞻断言应分别在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一个 位置拆分,但在 两个 位置 - 第二个其中似乎不正确——使用前瞻断言时。

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

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

我有两个问题:(Q1)这是怎么回事? (Q2)如何让strsplit() 表现得更好?


更新:Theodore Lytras 的出色回答解释了发生了什么,因此解决了 (Q1)。我的回答建立在他确定补救措施的基础上,解决了 (Q2)

【问题讨论】:

标签: regex r strsplit


【解决方案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.
}

注意,这意味着如果在开头有匹配 一个(非空)字符串,输出的第一个元素是'""',但是 如果字符串末尾有匹配项,则输出为 与删除匹配项相同。

问题在于前瞻(和后瞻)断言的长度为零。因此,例如在这种情况下:

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,并且前瞻匹配位置 1。但是没有什么可以删除,因为匹配的左侧没有任何内容,并且匹配本身的长度为零。所以算法陷入了无限循环。显然,R 通过拆分单个字符来解决这个问题,顺便提一下,当strspliting 使用空正则表达式时(当参数split="")时,记录的行为。在此之后,剩余的字符串是unky town,由于没有匹配,它作为最后一个拆分返回。

lookbehinds 没有问题,因为每个匹配项都被拆分并从剩余的字符串中删除,因此算法永远不会卡住。

诚然,这种行为乍一看很奇怪。然而,否则行为将违反前瞻零长度的假设。鉴于 strsplit 算法已记录在案,我相信这不符合错误的定义。

【讨论】:

  • 是的,这听起来很对。似乎是避免无限循环的不幸方式,但这似乎是算法strsplit 正在遵循。谢谢!
【解决方案2】:

根据 Theodore Lytras 对substr() 行为的仔细解释,一个相当干净的解决方法是在要匹配的前瞻断言前面加上一个匹配任何单个字符的肯定后向断言:

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

【讨论】:

  • @TheodoreLytras -- 谢谢!根据您的解释,我在尝试之前就知道它会起作用。
【解决方案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"

也许类似的东西可以作为一种解决方法。

【讨论】:

    猜你喜欢
    • 2020-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多