字符串是相同的(使用`base :: identical`),但在`grepl` /`gsub`中的行为不同

时间:2018-08-15 19:42:05

标签: r regex encoding

有关:Convert upper case words to title case

某些使用从网上获取的字符串的代码无法正常运行,您可以通过运行以下命令重现该问题:

library(xml2)
library(magrittr)
x <- xml2::read_html("https://poesie.webnet.fr/lesgrandsclassiques/Authors/B") %>%
  gsub("^.*?<span>(Pierre-Jean de BÉRANGER)</span>.*$","\\1",.)
x # [1] "Pierre-Jean de BÉRANGER"

此字符串与从页面源复制/粘贴的"Pierre-Jean de BÉRANGER"相同,但是以下行为令我非常烦恼:

y <- "Pierre-Jean de BÉRANGER"
x == y  # TRUE
identical(x, y) # TRUE
gsub("\\b([A-Z])(\\w+)\\b", "\\1\\L\\2", x, perl = TRUE) # [1] "Pierre-Jean de BÉRANGER"
gsub("\\b([A-Z])(\\w+)\\b", "\\1\\L\\2", y, perl = TRUE) # [1] "Pierre-Jean de Béranger"
grepl("\\bB\\w+", x, perl = TRUE) # FALSE
grepl("\\bB\\w+", y, perl = TRUE) # TRUE
grepl("\\bB\\w", x, perl = TRUE)  # TRUE
grepl("\\bB\\w", y, perl = TRUE)  # TRUE

如果xy相同,那么它们如何给出不同的输出?

?identical

  

测试两个对象是否完全相等的安全可靠方法


编辑:

有一个明显的区别:

Encoding(x) # "UTF-8"
Encoding(y) # "latin1"

我在R version 3.5.0上运行Windows

3 个答案:

答案 0 :(得分:4)

要解决该问题,您需要确保模式支持Unicode,以便\w可以匹配所有Unicode字母和数字,而\b可以匹配Unicode单词边界。这可以通过使用PCRE动词(*UCP)

gsub("(*UCP)\\b([A-Z])(\\w+)\\b", "\\1\\L\\2", x, perl = TRUE)
      ^^^^^^

要使其完全采用Unicode,请使用\p{Lu}而不是[A-Z]

gsub("(*UCP)\\b(\\p{Lu})(\\w+)\\b", "\\1\\L\\2", x, perl = TRUE)

此外,如果您不想匹配数字和_,则可以将\w替换为\p{L}(任何字母):

gsub("(*UCP)\\b(\\p{Lu})(\\p{L}+)\\b", "\\1\\L\\2", x, perl = TRUE)

答案 1 :(得分:1)

如果您检出source of the identical() function,则可以看到当它传递一个CHARSXP值(一个字符向量)时,它将调用内部帮助函数Seql()。比较之前先使用该函数converts string values to UTF。因此,identical并不是在检查编码是否一定相同,而只是在编码中嵌入的值相同。

在理想情况下,identical()函数除了您在进行比较时可以忽略的所有其他属性外,还应该具有ignore.encoding=选项。

但是从理论上讲,字符串实际上应该以相同的方式运行。因此,我想您可能会在这里指责regexpr引擎的“ perl”版本无法正确处理编码。基本的regexpr引擎似乎没有这个问题

grepl("B\\w+", x)
# [1] TRUE
grepl("B\\w+", y)
# [1] TRUE

答案 2 :(得分:0)

@MrFlick很好地解释了问题背后的原因,@Wiktor-Stribiżew提供了一个很好的解决方案,可以将perl regex引擎与混合编码一起使用,从而保留了原始编码。

现在看一下工作流程,我相信在实践中最好确保始终知道要使用哪种编码,并且只要可以接受,就可以在导入/获取步骤或之后立即协调所有操作。

在上述情况下,没有理由在检索外部数据后不立即协调编码,以免出现此类意外情况。

这可以通过执行第二步来完成:

x <- iconv(x, from="UTF-8", to="latin1")