【问题标题】:Regex optional capture groups in any order任何顺序的正则表达式可选捕获组
【发布时间】:2020-02-28 11:40:53
【问题描述】:

我想根据以任意顺序连续出现的匹配组来捕获组。而当一个集合类型在没有替代集合类型的情况下重复时,替代集合返回为 nil。

我正在尝试根据以下正则表达式提取姓名和电子邮件:

对于名称,两个连续的大写单词:

[A-Z][\w]+\s+[A-Z][\w]+

对于电子邮件:

\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b

示例文本:

John Doe john@doe.com random text
Jane Doe random text jane@doe.com
jim@doe.com  more random text tim@doe.com Tim Doe

到目前为止,我已经使用非捕获组和积极的前瞻性来解决“无特定顺序或什至存在”的问题,但只能通过换行符分段来解决。所以我的正则表达式看起来像这样:

^(?=(?:.*([A-Z][\w]+\s+[A-Z][\w]+))?)(?=(?:.*(\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b))?).*

结果会漏掉同一行有多个联系人的项目:

[
  ["John Doe", "john@doe.com"],
  ["Jane Doe", "jane@doe.com"],
  ["Tim Doe", "tim@doe.com"],
]

当我正在寻找的是:

[
  ["John Doe", "john@doe.com"],
  ["Jane Doe", "jane@doe.com"],
  [nil, "jim@doe.com"],
  ["Tim Doe", "tim@doe.com"],
]

我在正则表达式方面的技能有限,我开始使用正则表达式,因为它似乎是匹配姓名和电子邮件的最佳工具。

如果我们以这种方式提取数百个联系人,正则表达式是解决此类问题的最佳工具还是使用循环的更有效替代方法?

【问题讨论】:

  • 捕获电子邮件地址需要比这更复杂的模式,因为地址不一定是name@host.domain 格式。存在预先存在的模式,因此请搜索那些而不是编写自己的模式。扫描电子邮件文本也不能保证地址有效,只是它们与模式匹配。如果您确实想要有效地址,请向您的用户索取,然后向其发送一封电子邮件,要求对其进行验证。
  • 如果数据格式是随机的,那么抓取一个人的名字是不可能的。人们可以有一个单词的名字,也可以有多个单词的名字,他们可以连字符,包含句点等。同样,最好的方法是问他们更喜欢被称为什么,然后继续。如果您解释您要做什么,它可能会有所帮助。
  • "How to validate an email address using a regular expression?" 是一个很好的讨论,以及该页面右侧的“链接”问题。 regular-expressions.info/email.html 可能会有所帮助。
  • 为了说明@theTinMan 的观点,请看精彩文章Falsehoods Programmers Believe About Names (with examples)
  • 我不是在寻找捕获姓名和电子邮件的完美实现。我知道可能会有一些误报,尤其是对于名称。

标签: regex ruby regex-group


【解决方案1】:

您的文字已经太随意了,无法完成这项工作。有时甚至很难捕捉到更多的姓名和电子邮件。更高级的电子邮件模式只会有一点帮助。不仅有不寻常的电子邮件地址,还有各种狂野的名称模式。
D'arcy Bly、Markus-Anthony Reid、Lee Z 呢,这些可能是最简单的例子。

因此,除非您使用自然语言处理等更高级的技术,否则您必须做出很多假设并且不会完全满足。

如果你坚持你的方法,我想出了这个(没有牙齿的)怪物:

([A-Z]\w+ [A-Z]\w+)(?:\w* )*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})|
([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})(?:\w* )*([A-Z]\w+ [A-Z]\w+)|
([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})

交替组的顺序对于捕获流浪电子邮件很重要。

Demo

PS:我使用分支重置仅在组 1 和 2 中捕获的演示。但是,看起来 Ruby 2.x 不支持分支重置组。因此,您需要检查所有 5 个组的值。

【讨论】:

  • Ruby 不使用 PCRE,而是使用 Onigmo。如果您使用在线正则表达式测试器,则在处理 Ruby 时应使用Rubular,而不是 regex101。
  • @Amadan 谢谢,有道理。我经常担心那些其他正则表达式测试器网站只会将样本保留一小段时间。
  • 我同意这种可能性可能太随机而无法找到名称。简单的电子邮件地址很容易挑选,但仅此而已。而且,虽然电子邮件模式适用于简单的模式,但加入少量的 UUCP 和旧的大型机地址,它就会崩溃。
【解决方案2】:

这是将 @wp78de 的想法重写为 Ruby 正则表达式语法:

regexp = /
    (?<name>
      [A-Z][\w]+\s+[A-Z][\w]+
    ){0}
    (?<email>
      \b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b
    ){0}

    (?:
      \g<name> (?:\w*\s)* \g<email>
    | \g<email> (?:\w*\s)* \g<name>
    | \g<email>
    )
/x

text = <<-TEXT
John Doe john@doe.com random text
Jane Doe random text jane@doe.com
jim@doe.com  more random text tim@doe.com Tim Doe
TEXT

p text.scan(regexp)
# => [["John Doe", "john@doe.com"],
# =>  ["Jane Doe", "jane@doe.com"],
# =>  [nil, "jim@doe.com"],
# =>  ["Tim Doe", "tim@doe.com"]]

【讨论】:

  • 有趣。看起来 Perl 和 Ruby 允许使用相同名称的组,或者为什么将不同的捕获组合在一起?
  • 我相信[\w]\w 是一样的。我喜欢{0}。最近刚读到。
  • @theTinMan 我完全同意你所有的 cmets,无论是在这里还是在问题下。正如我上面所说,我的答案主要是关于如何将 wp78de 的答案中的(|...) 构造翻译成 Onigmo,并且我没有修改 OP 的子表达式。
  • @wp78de (?&lt;name&gt;...) 是一个命名的捕获模式。但是,我让它在模式开始时匹配零次,所以它实际上并没有做任何事情。 Onigmo 可以使用\k&lt;name&gt; 构造(甚至递归)运行子模式,因此实际捕获发生在那里。如果多次捕获,Onigmo 将覆盖捕获组(例如,(?g&lt;ch&gt;\w)\g&lt;ch&gt; 匹配 "ab" 将产生 { "ch" =&gt; "b" };但这里不会多次捕获(第一次提及重复 0 次,其他提及是排他性的)交替)。
  • @theTinMan Lol.... 听错了吗? :D(我做了很多)我对你的回答也有同样的感觉,总是信息丰富!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-21
  • 2018-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多