我有一些文字,其中人们使用大写,其间有空格,以使子串突出.我想替换这些子串之间的空格.该模式的规则是:"至少3个连续的大写字母,每个字母之间有一个空格".
我很好奇如何使用纯正则表达式以及gsubfn包这样做,因为我认为这对它来说是一件容易的工作但是在下面的MWE示例中我因为额外的字母放在那里而崩溃和烧毁(我很好奇为什么会发生这种情况).
x <- c(
    'Welcome to A I: the best W O R L D!',
    'Hi I R is the B O M B for sure: we A G R E E indeed.'
)
## first to show I have the right regex pattern
gsub('(([A-Z]\\s+){2,}[A-Z])', '<FOO>', x)
## [1] "Welcome to A I: the best <FOO>!"               
## [2] "Hi I R is the <FOO> for sure: we <FOO> indeed."
library(gsubfn)
spacrm1 <- function(string) {gsub('\\s+', '', string)}
gsubfn('(([A-Z]\\s+){2,}[A-Z])', spacrm1, x)
## Error in (function (string)  : unused argument ("L ")
## "Would love to understand why this error is happening"
spacrm2 <- function(...) {gsub('\\s+', '', paste(..., collapse = ''))}
gsubfn('(([A-Z]\\s+){2,}[A-Z])', spacrm2, x)
## [1] "Welcome to A I: the best WORLDL!"               
## [2] "Hi I R is the BOMBM for sure: we AGREEE indeed."
## "Would love to understand why the extra letter is happening"
[1] "Welcome to A I: the best WORLD!"                 
[2] "Hi I R is the BOMB for sure: we AGREE indeed."
正如我在评论中指出的那样,问题中第一个gsubfn调用中的问题来自于正则表达式中有两个捕获组,但函数只有一个参数.这些需要匹配 - 两个捕获组意味着需要两个参数.我们可以通过运行它并查看print语句的输出来查看gsubfn传递的内容:
junk <- gsubfn('(([A-Z]\\s+){2,}[A-Z])', ~ print(list(...)), x)
我们可以通过以下任何方式解决此问题:
1)这使用了问题中的正则表达式,但使用了一个接受多个参数的函数.实际上只在函数中使用了第一个参数.
gsubfn('(([A-Z]\\s+){2,}[A-Z])', ~ gsub("\\s+", "", ..1), x)
## [1] "Welcome to A I: the best WORLD!"              
## [2] "Hi I R is the BOMB for sure: we AGREE indeed."
请注意,它将公式解释为函数:
function (...) gsub("\\s+", "", ..1)
我们可以像这样查看公式生成的函数:
fn$identity( ~ gsub("\\s+", "", ..1) )
## function (...) 
## gsub("\\s+", "", ..1)
2)这使用了问题的正则表达式以及问题中的函数,但添加了backref = -1参数,该参数告诉它只将第一个捕获组传递给函数 - 减号表示不传递整个匹配.
gsubfn('(([A-Z]\\s+){2,}[A-Z])', spacrm1, x, backref = -1)
(正如@WiktorStribiżew在他的回答中指出的那样backref=0也会奏效.)
3)使用问题中的正则表达式来表达这一点的另一种方法是:
gsubfn('(([A-Z]\\s+){2,}[A-Z])', x + y ~ gsub("\\s+", "", x), x)
请注意,它将公式解释为此函数:
function(x, y) gsub("\\s+", "", x)
在R中有一种方法可以完全使用正则表达式,但它并不漂亮(虽然我觉得它看起来很漂亮!)这个答案也可以根据你的需要定制(两个大写最小,三个最小等) - 即可伸缩 - 并且可以匹配多个水平空白字符(不使用需要固定宽度的lookbehinds).
(?:(?=\b(?:\p{Lu}\h+){2}\p{Lu})|\G(?!\A))\p{Lu}\K\h+(?=\p{Lu})
替换:空字符串
我刚刚意识到我\b在这里使用过,这可能不适用于Unicode字符(例如É).以下替代方案可能是更好的方法.它检查以确保在第一个大写字符不是字母(来自任何语言/脚本)之前的内容.它还确保它与大写系列末尾的大写字符不匹配,如果后跟任何其他字母.
如果您还需要确保数字不在大写字母之前,则可以使用[^\p{L}\p{N}]代替\P{L}.
(?:(?<=\P{L})(?=(?:\p{Lu}\h+){2}\p{Lu})|\G(?!\A))\p{Lu}\K\h+(?=\p{Lu}(?!\p{L}))
x <- c(
    "Welcome to A I: the best W O R L D!",
    "Hi I R is the B O M B for sure: we A G R E E indeed."
)
gsub("(?:(?=\\b(?:\\p{Lu}\\h+){2}\\p{Lu})|\\G(?!\\A))\\p{Lu}\\K\\h+(?=\\p{Lu})", "", x, perl=TRUE)
Welcome to A I: the best W O R L D!
Hi I R is the B O M B for sure: we A G R E E indeed.
Welcome to A I: the best WORLD!
Hi I R is the BOMB for sure: we AGREE indeed.
(?:(?=(?:\b\p{Lu}\h+){2}\p{Lu})|\G(?!\A)) 匹配以下任一项
(?=\b(?:\p{Lu}\h+){2}\p{Lu})确保后续匹配的正向前瞻(在这种情况下用作断言以查找字符串中格式中的所有位置A A A).您也可以\b在这个积极的前瞻性末尾添加,以确保I A Name不匹配的
 东西\b 在字边界处断言位置(?:\p{Lu}\h+){2} 完全匹配以下两次
\p{Lu} 匹配任何语言的大写字符(Unicode)\h+ 匹配一个或多个水平空白字符\p{Lu} 匹配任何语言的大写字符(Unicode)\G(?!\A) 在上一场比赛结束时断言位置\p{Lu} 匹配任何语言的大写字符(Unicode)\K重置报告的匹配的起点.最终匹配中不再包含任何先前消费的字符\h+ 匹配一个或多个水平空白字符(?=\p{Lu}) 确保以下内容的正向前瞻是任何语言的大写字符(Unicode)这里的问题是什么项传递给spacrm函数,函数accept gsubfn的参数数量不匹配spacrm以及传递给它们的参数数量.
查看有关参数的gsubfn文档backref:
要传递给函数的反向引用数.如果为零或正,则匹配作为替换函数的第一个参数传递,后跟指示的反向引用数作为后续参数.如果为负,则仅传递该反向引用数,但匹配本身不通过.如果省略,它将自动确定,即如果没有反向引用它将为0,否则它将等于反向引用的数量.它通过计算模式中未转义的左括号的数量来确定这一点.
因此,在您的情况下,backref参数被省略,spacrmX函数得到W O R L D和L 值.
该   spacrm1只接受一个参数函数有两个参数,因此unused argument ("L ")错误.
当spacrm2使用时,它获得了所有两个捕获的值,并且它们被连接(在删除空白之后).
您实际上可能只是backref=0用来告诉gsubfn只处理整个匹配值并简化模式,删除捕获组并使用一个非捕获:
spacrm1 <- function(string) {gsub('\\s+', '', string)}
x <- c(
     'Welcome to A I: the best W O R L D!',
     'Hi I R is the B O M B for sure: we A G R E E indeed.'
)
gsubfn('(?:[A-Z]\\s+){2,}[A-Z]', spacrm2, x, backref=0)
[1] "Welcome to A I: the best WORLD!"              
[2] "Hi I R is the BOMB for sure: we AGREE indeed."