【问题标题】:Replace the spaces between multiple (3+) capital letters替换多个 (3+) 大写字母之间的空格
【发布时间】:2018-05-06 06:47:42
【问题描述】:

我有一些文本,人们使用大写字母和中间的空格来突出子字符串。我想替换这些子字符串之间的空格。该模式的规则是:“至少 3 个连续的大写字母,每个字母之间有一个空格”。

我很好奇如何使用纯正则表达式以及 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('(([A-Z]\\s+){2,}[A-Z])', ~ print(list(...)), x)
  • 是的,看起来参数数量不匹配。如果您将spacrm2gsubfn('((?:[A-Z]\\s+){2,}[A-Z])', spacrm2, x) 一起使用,则结果符合预期。
  • @WiktorStribiżew 你能给出答案吗
  • 啊,我终于想起来了:要想通过整场比赛,你需要通过backref=0参数。

标签: r regex gsubfn


【解决方案1】:

概述

在 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 字符(例如É)。以下替代方法可能是更好的方法。它检查以确保第一个大写字符之前的不是字母(来自任何语言/脚本)。它还确保它不匹配大写系列末尾的大写字符,如果它后面跟着任何其他字母。

如果您还需要确保数字不在大写字母前面,可以使用[^\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}))

使用回调

请参阅Wiktor's original answer 关于回调,这只是将他的 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))

【讨论】:

  • 太棒了...我自己不会想到这个。在这里学习很棒。
  • 我一直试图在 python 中复制它,但不幸的是没有取得太大进展。 @ctwheels,任何帮助将不胜感激。
  • @Raqib 我在我的答案中添加了一个编辑以包含 python 变体
  • @chtwheels。非常感谢你的帮助。 ?附加问题,有没有办法纯粹使用python内置的re库来做到这一点?我尽量避免使用第三方库,但如果这是唯一的解决方案,那么我对使用该库感到满意。再次感谢你,你救了。我相当头痛。
  • @Raqib 你也许可以使用回调,我在 Edit 2 中添加了一个新的编辑,但它不适用于 Unicode。您必须为 Unicode 创建自己的解析器,因为默认 re 库没有 Unicode 类,因此 \P{Lu} 将不起作用。您必须使用islower()isdigit() 来实现该代码端以识别Unicode 变体并将正则表达式更改为(?:[^\W_]\s+){2,}[^\W_] + 使用re.UNICODE 以确保匹配Unicode 字符
【解决方案2】:

正如我在 cmets 中指出的那样,问题中第一个 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)

(正如@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)

【讨论】:

  • 所有很棒的答案...这是检查的第一个答案
【解决方案3】:

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

参见gsubfn docs 关于backref 的论点:

要传递给函数的反向引用数。如果为零或正,则匹配作为第一个参数传递给替换函数,然后是指定数量的反向引用作为后续参数。 如果为负数,则仅传递该数量的反向引用,但匹配本身不是。如果省略,将自动确定,即如果没有反向引用,则为 0 否则它将等于反向引用的数量为负数。它通过计算模式中非转义左括号的数量来确定这一点。

因此,在您的情况下,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."

【讨论】:

    【解决方案4】:

    您可以简单地匹配前面有一个大写字母的空格,以及后面两个用空格分隔的大写字母(使用环视)。 反之亦然 - 匹配一个空格,前面有两个大写字母,由一个空格隔开,然后是一个大写字母。

    (?<=[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.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-16
      • 1970-01-01
      • 2019-03-31
      • 1970-01-01
      • 2021-08-18
      • 1970-01-01
      • 1970-01-01
      • 2015-04-28
      相关资源
      最近更新 更多