替换多个 (3+) 大写字母之间的空格

Replace the spaces between multiple (3+) capital letters

我有一些文本,其中人们使用大写字母,中间有空格,以使子字符串突出。我想替换这些子字符串之间的空格。该模式的规则是:"at least 3 consecutive capital letters with a space between each letter"。

我很好奇如何使用纯正则表达式以及 gsubfn 包来做到这一点,因为我认为这对它来说是一件容易的事,但在下面的 MWE 示例中我崩溃了并在其中放了一封额外的信后被烧毁(我很好奇为什么会这样)。

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 调用的问题是由于正则表达式中有两个捕获组,但函数只有一个参数。这些需要匹配——两个捕获组意味着需要两个参数。我们可以看到 gsubfn 正在通过 运行 this 并查看 print 语句的输出:

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)

(正如@Wiktor Stribiżew 在他的回答中指出的那样 backref=0 也可以。)

3) 使用问题中的正则表达式表达这一点的另一种方法是:

gsubfn('(([A-Z]\s+){2,}[A-Z])', x + y ~ gsub("\s+", "", x), x)

注意它将公式解释为这个函数:

function(x, y) gsub("\s+", "", x)

这里的问题是 gsubfn 将哪些项目传递给 spacrm 函数,以及 spacrm 函数接受的参数数量与传递给它们的参数数量不匹配.

参见关于 backref 参数的 gsubfn docs

Number of backreferences to be passed to function. If zero or positive the match is passed as the first argument to the replacement function followed by the indicated number of backreferences as subsequent arguments. If negative then only the that number of backreferences are passed but the match itself is not. If omitted it will be determined automatically, i.e. it will be 0 if there are no backreferences and otherwise it will equal negative the number of back references. It determines this by counting the number of non-escaped left parentheses in the pattern.

因此,在您的情况下,省略了 backref 参数,并且 spacrmX 函数 got W O R L D and 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."

概述

R 中有一种方法可以完全使用正则表达式来完成此操作,但它并不漂亮(尽管我认为它看起来很不错!)这个答案也可以根据您的需要进行自定义(最少两个大写,最少三个,等)- 即可缩放 - 并且可以匹配多个水平空白字符(不使用 lookbehinds,它需要一个固定的宽度)。


代码

See regex in use here

(?:(?=\b(?:\p{Lu}\h+){2}\p{Lu})|\G(?!\A))\p{Lu}\K\h+(?=\p{Lu})

替换:空字符串


编辑 1(非 ASCII 字母)

我的原始模式使用 \b,它可能不适用于 Unicode 字符(例如 É)。以下替代方法可能是更好的方法。它检查以确保第一个大写字符之前的不是字母(来自任何 language/script)。它还确保它不匹配大写系列末尾的大写字符(如果它后面跟着任何其他字母)。

如果您还需要确保数字不在大写字母之前,您可以使用 [^\p{L}\p{N}] 代替 \P{L}

See regex in use here

(?:(?<=\P{L})(?=(?:\p{Lu}\h+){2}\p{Lu})|\G(?!\A))\p{Lu}\K\h+(?=\p{Lu}(?!\p{L}))

用法

See code in use here

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) 中的大写字符的积极前瞻

编辑 2 (python)

下面是上面的 python 等价物(它需要 PyPi regex 到 运行)。我用 [ \t] 替换了 \h 因为 PyPi 正则表达式目前不支持 \h 令牌。

See the working code here

import regex
a = [
    "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."
]

r = regex.compile(r"(?:(?=\b(?:\p{Lu} +){2}\p{Lu})|\G(?!\A))\p{Lu}\K +(?=\p{Lu})")
for i in a:
    print(r.sub('',i))

以上正则表达式基于第一个正则表达式。如果您想使用第二个正则表达式,请使用:

(?:(?<=\P{L})(?=(?:\p{Lu}[ \t]+){2}\p{Lu})|\G(?!\A))\p{Lu}\K[ \t]+(?=\p{Lu}(?!\p{L}))

使用回调

请参阅 关于回调,这只是他的 R 程序到 python 的移植版本。这不使用 PyPi 正则表达式库,因此它不会匹配。此外,这与 Unicode 不匹配。

import re
a = [
    "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."
]

def repl(m):
    return re.sub(r"\s+",'',m.group(0))

for i in a:
    print(re.sub(r"(?:[A-Z]\s+){2,}[A-Z]", repl, i))

您可以简单地匹配前面有大写字母的 space,以及后面由 space 分隔的两个大写字母(使用环视)。 或者相反 - 匹配一个 space 前面有两个大写字母,中间用 space 分隔,然后是一个大写字母。

(?<=[A-Z]) (?=[A-Z] [A-Z])|(?<=[A-Z] [A-Z]) (?=[A-Z])

R 代码:

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("(?<=[A-Z]) (?=[A-Z] [A-Z])|(?<=[A-Z] [A-Z]) (?=[A-Z])", "", x, perl=TRUE)

Live here at ideone.