【问题标题】:regex replace parts/groups of a string in R正则表达式替换 R 中字符串的部分/组
【发布时间】:2020-02-20 21:37:01
【问题描述】:

尝试对 bookdown 文档的 LaTeX(pdf_book 输出)进行后处理,以折叠 biblatex 引文,以便稍后使用\usepackage[sortcites]{biblatex} 按时间顺序对它们进行排序。因此,我需要在\\autocites 之后找到}{ 并将其替换为,。我正在尝试gsub(),但找不到正确的咒语。

# example input
testcase <- "text \\autocites[cf.~][]{foxMapping2000}{wattPattern1947}{runkleGap1990} text {keep}{separate}"

# desired output
"text \\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990} text {keep}{separate}"

一个简单的方法是替换所有}{

> gsub('\\}\\{', ',', testcase, perl=TRUE)
[1] "text \\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990} text {keep,separate}"

但这也会崩溃{keep}{separate}

然后我试图通过使用不同的组来替换以\\autocites 开头的“单词”(没有空格的字符串)中的}{,但失败了:

> gsub('(\\\\autocites)([^ \f\n\r\t\v}{}]+)((\\}\\{})+)', '\\1\\2\\3', testcase, perl=TRUE)
[1] "text \\autocites[cf.~][]{foxMapping2000}{wattPattern1947}{runkleGap1990} some text {keep}{separate}"

附录: 实际文档包含比上面的测试用例更多的行/元素。并非所有元素都包含\\autocites,在极少数情况下,一个元素包含多个\\autocites。我最初并不认为这是相关的。更真实的测试用例:

testcase2 <- c("some text",
"text \\autocites[cf.~][]{foxMapping2000}{wattPattern1947}{runkleGap1990} text {keep}{separate}",
"text \\autocites[cf.~][]{foxMapping2000}{wattPattern1947}{runkleGap1990} text {keep}{separate} \\autocites[cf.~][]{foxMapping2000}{wattPattern1947}")

【问题讨论】:

  • 额外的“\\autocites”段也应该同样编辑?
  • 是的,所有 '}{' 都需要转换为 ',' 直到所有 '\\autocites'-strings 的空格
  • 那么解胶就不行了,如果能解决你的问题,我建议接受Wiktor的解决方案。

标签: r regex gsub capture-group


【解决方案1】:

一个gsub 电话就足够了:

gsub("(?:\\G(?!^)|\\\\autocites)\\S*?\\K}{", ",", testcase, perl=TRUE)
## => [1] "text \\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990} text {keep}{separate}"

请参阅regex demo。这里,(?:\G(?!^)|\\autocites) 匹配上一个匹配的结尾或\autocites 字符串,然后它匹配任何0 个或多个非空白字符,但尽可能少,然后\K 丢弃当前匹配缓冲区中的文本并消耗}{ 子字符串最终被逗号替换。

还有一个非常易读的解决方案,其中一个正则表达式和一个使用stringr::str_replace_all 的固定文本替换:

library(stringr)
str_replace_all(testcase, "\\\\autocites\\S+", function(x) gsub("}{", ",", x, fixed=TRUE))
# => [1] "text \\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990} text {keep}{separate}"

在这里,\\autocites\S+ 匹配 \autocites,然后匹配 1+ 个非空白字符,gsub("}{", ",", x, fixed=TRUE) 将匹配文本中的每个 }{ 替换(非常快),

【讨论】:

  • 令人印象深刻!它也适用于新的 testcase2。
  • 您的“gsub”解决方案是迄今为止最快的。 “while-length-grep”方法大约慢 4 倍,“str_replace_all()”大约慢 20 倍(使用 testcase2 进行基准测试)。
  • 不熟悉\G\K。谢谢!你能解释一下 \G 之后的负前瞻的目的吗?
  • @iod \G operator 匹配两个位置:1)字符串的开头和 2)上一个成功匹配的结尾。通过添加(?!^)(或(?&lt;!^)/(?&lt;!\A)/(?!\A))字符串位置的开始被排除。
【解决方案2】:

我发现了一个有效的咒语。不好看:

gsub("\\\\autocites[^ ]*",
  gsub("\\}\\{",",",
    gsub(".*(\\\\autocites[^ ]*).*","\\\\\\1",testcase) #all those extra backslashes are there because R is ridiculous.
    ),
  testcase)

我把它分成几行,希望让它更容易理解。基本上,最里面的gsub 只提取自动标记(\\autocites 后面的任何内容直到第一个空格),然后中间的gsub 用逗号替换}{s,最外面的gsub 替换结果中间的那个是最里面的那个抽取出来的图案。

当然,这只适用于字符串中的单个自动引用。

另外,fortune(365)

【讨论】:

  • 您的解决方案看起来很棒。用真实的数据集测试它,我发现我实际上有不止一个'\\autocites'的元素。抱歉,测试用例不准确。
【解决方案3】:

我将输入字符串稍大一点,以使算法更清晰。

str <- "
text \\autocites[cf.~][]{foxMapping2000}{wattPattern1947}{runkleGap1990} text {keep}{separate}
text \\autocites[cf.~][]{wattPattern1947}{foxMapping2000}{runkleGap1990} text {keep}{separate}
"

我们将首先提取所有引用块,将其中的"}{"替换为",",然后将它们放回字符串中。

# pattern for matching citation blocks
pattern <- "\\\\autocites(\\[[^\\[\\]]*\\])*(\\{[[:alnum:]]*\\})+"
cit <- str_extract_all(str, pattern)[[1]]
cit

#> [1] "\\autocites[cf.~][]{foxMapping2000}{wattPattern1947}{runkleGap1990}"
#> [2] "\\autocites[cf.~][]{wattPattern1947}{foxMapping2000}{runkleGap1990}"

在引用块中替换:

newcit <- str_replace_all(cit, "\\}\\{", ",")
newcit
#> [1] "\\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990}"
#> [2] "\\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990}"

在找到引用块的地方打破原来的字符串

strspl <- str_split(str, pattern)[[1]]
strspl
#> [1] "\ntext "  " text {keep}{separate}\ntext "  " text {keep}{separate}\n"

插入修改后的引用块:

combined <- character(length(strspl) + length(newcit))
combined[c(TRUE, FALSE)] <- strspl
combined[c(FALSE, TRUE)] <- newcit
combined
#> [1] "\ntext "                                                          
#> [2] "\\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990}"
#> [3] " text {keep}{separate}\ntext "                                    
#> [4] "\\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990}"
#> [5] " text {keep}{separate}\n"

将其粘贴在一起以完成:

newstr <- paste(combined, collapse = "")
newstr
#> [1] "\ntext \\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990} text {keep}{separate}\ntext \\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990} text {keep}{separate}\n"

我怀疑可能有基于相同想法的更优雅的完全正则表达式解决方案,但我找不到。

【讨论】:

  • 请参阅上面我建议的解决方案,以获取相同想法的单行、完全正则表达式实现。
  • 是的,有这样的正则表达式,见my answer
【解决方案4】:

不是最漂亮的解决方案,但它有效。这会反复将 }{ 替换为 ,但前提是它遵循 autocities 且中间没有空格。

while(length(grep('(autocites\\S*)\\}\\{', testcase, perl=TRUE))) {
    testcase = sub('(autocites\\S*)\\}\\{', '\\1,', testcase, perl=TRUE)
}

testcase
[1] "text \\autocites[cf.~][]{foxMapping2000,wattPattern1947,runkleGap1990} text {keep}{separate}"

【讨论】:

  • 您的解决方案效果很好。它还可以很好地处理 testcase2。
猜你喜欢
  • 2017-11-27
  • 1970-01-01
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
相关资源
最近更新 更多