【问题标题】:Regex: Match all characters between two strings正则表达式:匹配两个字符串之间的所有字符
【发布时间】:2020-03-22 19:50:00
【问题描述】:

示例:在荷兰,花生酱被称为“pindakaas”(花生奶酪)而不是“pindaboter”(花生酱),因为黄油这个词只应该用于含有实际黄油的产品。

我想匹配 cheesebutter 之间的所有内容,反之亦然。

目标:

  • 黄油被称为“pindakaas”(花生奶酪
  • 奶酪)而不是“pindaboter”(花生酱

编辑: 使用的语言是 Python 3.7,我使用的当前正则表达式是 cheese(.*?)butter

【问题讨论】:

  • 你使用什么语言,你做了什么尝试?
  • 我编辑了帖子

标签: regex python-3.x pattern-matching


【解决方案1】:

您的示例中的问题是,您的第一个目标的最后一个单词是源字符串中第二个目标的第一个单词; “正常”RegEx 语法使 RE 引擎消耗它匹配的字符,即这些字符不可用于进一步匹配。

原则上,您可以使用 PCRE 等 Regex 风格,在前瞻断言中使用捕获组,因为它们不会导致断言中的字符消耗。但是,所有这些断言都有其性能的代价。比赛将在两个捕获组中。两个例子:

直截了当:

/
\b(?=(.*?cheese))butter  # match butter, assert that cheese comes after it and capture
|                        # or
\b(?=(.*?butter))cheese  # match cheese, assert that butter comes after it and capture
/gsx                     # flags: global, single line, free spacing

让我们看看\b(?=(.*?cheese))butter的成功匹配是如何工作的;同样的原则也反映在另一种选择中。正则表达式引擎首先查找单词边界\b,即文本中两边都没有单词字符的位置。一旦找到,它将尝试在此位置断言(?=(.*?cheese))。在自然语言中:“从这里开始,尽快找到cheese。只有找到了,才将刚刚遍历的整个字符串捕获在一个编号组中,并将匹配指针返回到我们开始的位置。然后,允许匹配继续。”如果断言成功,则匹配继续,然后使用 butter。我们有了匹配项,匹配指针位于butter 后面,正则表达式引擎对文本的其余部分尝试相同的操作(当然也包括替代项)。

regex demo

稍微优化的版本:

/
\b(?=((?:[^c]*+|c(?!heese))*cheese))butter
|
\b(?=((?:[^b]*+|b(?!utter))*butter))cheese
/gsx

regex demo

输出:

Match 1
Full match  27-33   butter
Group 1.    27-70   butter is called "pindakaas" (peanut cheese

Match 2
Full match  64-70   cheese
Group 2.    64-111  cheese) rather than "pindaboter" (peanut butter

如果不反对在事后为每个匹配连接匹配的字符串和捕获的字符串,这也可以工作,并且在性能方面会更好。 (仍然可能不如 Booboo 的答案中看到的 overlap 解决方案。)

/\bbutter\b.*?\b(?=(cheese))|\bcheese\b.*?\b(?=(butter))/sg

这只会匹配每个替代项,直到第二个术语之前的单词边界,这允许下一次匹配尝试从该术语开始。第二项不是匹配字符串的一部分,而是存储在捕获的组中:['butter is called "pindakaas" (peanut ', 'cheese'], etc.

regex demo

【讨论】:

  • 这对我的正则表达式教育非常有帮助。你能解释一下正则表达式引擎(比如 PCRE)如何处理“Straight appoach”正则表达式吗?此外,如果只有| 之前的部分存在,则它只匹配一个子字符串。为什么后面的也不行?一件小事:你需要更多的分词来避免匹配“酪乳”和“头奶酪”。
  • 您对单词边界的看法是正确的,但如果只是因为其他单词对示例没有帮助,我会将它们留在这里。不过,适当地注意到了。第一个选项没有多次匹配,因为示例中只出现了一次奶酪。我将尝试解释断言部分在编辑中是如何工作的。 (我刚刚发现你可以增加帖子编辑框的大小......哈哈。)
  • 让我解释一下我认为正在发生的事情(这一定是错误的),然后您可以纠正我。最初\b(?=(.*?cheese)) 是成功的,并且 cap grp 1 包含从字符串开头到第一次出现“cheese”(在第一次出现“butter”之后)的文本。指针现在回到字符串的开头。接下来,匹配“黄油”的第一个实例。那么 cap grp 1 和 "butter" 的内容如何在 "butter" 的第一个实例和 "butter" 实例之后的第一个 "cheese" 实例的结尾之间生成字符串?
  • butter 作为匹配与在这种情况下存储在捕获组中的内容无关。如果在成功断言之后,引擎无法在单词边界之后匹配butter,那么它将简单地丢弃所有内容并在匹配指针的下一个停止处从头开始。只有当butter 匹配时,才会保留捕获并进入结果。编辑:我不会在我的解释中查看失败的断言或匹配;当然,作为一个整体的替代方案将在文本的所有位置上失败,除了一个。
  • 知道了!非常感谢!因此引擎在字符串中的第一个字符之前启动并失败,因为字符串不是以“黄油”开头。然后它移动到字符串的第二个字符并再次失败。它一直持续到第一次出现“黄油”的“b”之前。现在捕获组将包含“butter...cheese”,并且指针返回到匹配“b”、“butter”之前的右侧,匹配成功并且捕获组包含我们想要的内容。对吗?
【解决方案2】:

如果您从PyPI 存储库安装regex 软件包,那么您可以执行overlapped 搜索:

import regex as re

text = 'In the Netherlands, peanut butter is called "pindakaas" (peanut cheese) rather than "pindaboter" (peanut butter) because the word butter is only supposed to be used with products that contain actual butter.'

l = re.findall(r'\bbutter\b.*?\bcheese\b|\bcheese\b.*?\bbutter\b', text, overlapped=True)
print(l)

打印:

['butter is called "pindakaas" (peanut cheese', 'cheese) rather than "pindaboter" (peanut butter']

我使用了您的基本正则表达式,但要求 buttercheese 位于单词边界上,例如\bbutter\b,在单词前后放置\b。随意删除或不删除。

【讨论】:

  • 简洁的功能,我想要两个小时的生命回来。 ^^
  • 我应该添加通常的人:import regex as re 并将其用作re 的替代品。我将更新我的答案以反映正常使用情况。
猜你喜欢
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
  • 2016-02-04
  • 1970-01-01
相关资源
最近更新 更多