python 部分正则匹配

6l7fqoea  于 2023-05-27  发布在  Python
关注(0)|答案(3)|浏览(124)

我正在查询Python中的部分正则表达式匹配。

例如:

如果你有一个字符串:

string = 'foo bar cat dog elephant barn yarn p n a'

和一个正则表达式:

pattern = r'foo bar cat barn yard p n a f'

以下情况为真:

  • re.match(pattern, string)将返回None
  • re.search(pattern, string)也将返回None

尽管我们都可以看到模式的第一部分与字符串的第一部分相匹配。
那么,除了在字符串中搜索整个模式之外,有没有一种方法可以查看字符串中与模式匹配的百分比?

l7mqbcuq

l7mqbcuq1#

是的,可以进行部分正则表达式匹配

我一直在尝试部分匹配的想法,并在搜索过程中发现了这个Q。我找到了一种方法来做我需要的,并认为我会在这里发布。
这不是速度恶魔。可能只有在速度不是问题的情况下才有用。
此函数查找正则表达式的最佳部分匹配并返回匹配文本。

>>> def partial_match(regex, string, flags=0, op=re.match):
...     """
...     Matches a regular expression to a string incrementally, retaining the
...     best substring matched.
...     :param regex:   The regular expression to apply to the string.
...     :param string:  The target string.
...     :param flags:   re module flags, e.g.: `re.I`
...     :param op:      Either of re.match (default) or re.search.
...     :return:        The substring of the best partial match.
...     """
...     m = op(regex, string, flags)
...     if m:
...         return m.group(0)
...     final = None
...     for i in range(1, len(regex) + 1):
...         try:
...             m = op(regex[:i], string, flags)
...             if m:
...                 final = m.group(0)
...         except re.error:
...             pass
...     return final
...

测试它:

>>> partial_match(r".*l.*?iardvark", "bluebird")
'bluebi'
>>> 
>>> partial_match(r"l.*?iardvark", "bluebird")
>>> # None was returned. Try again with search...
>>> 
>>> partial_match(r"l.*?iardvark", "bluebird", op=re.search)
'luebi'
>>>
>>> string = 'foo bar cat dog elephant barn yarn p n a'
>>> pattern = r'foo bar cat barn yard p n a f'
>>> 
>>> partial_match(pattern, string)
'foo bar cat '
>>> 
>>> partial_match(r".* (zoo){1,3}ran away", "the fox at the "
...                                         "zoozoozoozoozoo is "
...                                         "happy")
'the fox at the zoozoozoo'

表现如预期。该算法不断尝试将尽可能多的表达式与目标字符串进行匹配。它将继续,直到整个表达式都与目标字符串匹配,并保留最佳部分匹配。
好吧。现在让我们看看它到底有多慢…

>>> import cProfile as cprof, random as rand, re
>>>
>>> # targets = ['lazy: that# fox~ The; little@ quick! lamb^ dog~ ',
>>> #            << 999 more random strings of random length >>]
>>>
>>> words = """The; quick! brown? fox~ jumped, over. the! lazy: dog~
...            Mary? had. a little- lamb, a& little@ lamb^ {was} she... and,,, 
...            [everywhere] that# Mary* went=, the. "lamb" was; sure() (to) be.
...         """.split()
...
>>> targets = [' '.join(rand.choices(words, k=rand.randint(1, 100))) 
...            for _ in range(1000)]
...
>>> exprs   = ['.*?&', '.*(jumped|and|;)', '.{1,100}[\\.,;&#^]', '.*?!', 
...            '.*?dog. .?lamb.?', '.*?@', 'lamb', 'Mary']
...
>>> partial_match_script = """
... for t in targets:
...     for e in exprs:
...         m = partial_match(e, t)
...         """
...
>>> match_script = """
... for t in targets:
...     for e in exprs:
...         m = re.match(e, t)
...         """
... 
>>> cprof.run(match_script)
         32003 function calls in 0.032 seconds
>>>
>>> cprof.run(partial_match_script)
         261949 function calls (258167 primitive calls) in 0.230 seconds
  • re.match()直接运行它,它只需要进行常规匹配,并且大部分时间都失败了,这可能不是对函数性能的公平比较。一个更好的比较是针对一个支持模糊匹配的模块,我在下面做了。而且函数运行良好。*

可以使用re.sre_parse和/或re.sre_compile模块开发性能更高的解决方案。看起来所有的文档都在源代码和网络上的一些片段中,比如https://www.programcreek.com/python/example/1434/sre_parse
有了这些模块,我认为可能有一种方法可以通过标记或子表达式来递增地应用正则表达式,而不是像我所做的那样通过单个字符。
此外,正如有人评论的那样,正则表达式包具有模糊匹配功能(https://pypi.org/project/regex/)-但它的行为略有不同,可能允许部分匹配中出现意外字符。

>>> import regex
>>>
>>> regex.match(r"(?:a.b.c.d){d}", "a.b.c", regex.ENHANCEMATCH).group(0)
'a.b.c'
>>> regex.match(r"(?:moo ow dog cat){d}", "moo cow house car").group(0)
'moo c'
>>> regex.match(r"(?:moo ow dog cat){d}", "moo cow house car", 
...             regex.ENHANCEMATCH).group(0)
...
'moo c'
>>> # ^^ the 'c' above is not what we want in the output. As you can see,
>>> # the 'fuzzy' matching is a bit different from partial matching.
>>>
>>> regex_script = """
... for t in targets:
...     for e in exprs:
...         m = regex.match(rf"(?:{e}){{d}}", t)
...         """
>>>
>>> cprof.run(regex_script)
         57912 function calls (57835 primitive calls) in 0.180 seconds
...
>>> regex_script = """
... for t in targets:
...     for e in exprs:
...         m = regex.match(rf"(?:{e}){{d}}", t, flags=regex.ENHANCEMATCH)
...         """
>>> 
>>> cprof.run(regex_script)
         57904 function calls (57827 primitive calls) in 0.298 seconds

性能比没有regex.ENHANCEMATCH标志的partial_match()解决方案稍好。不过,有旗子的话速度会慢一些。
带有regex.BESTMATCH标志的正则表达式在行为上可能与partial_match()最相似,但它甚至更慢:

>>> regex_script = """
... for t in targets:
...     for e in exprs:
...         m = regex.match(rf"(?:{e}){{d}}", t, flags=regex.BESTMATCH)
...         """
>>> cprof.run(regex_script)
         57912 function calls (57835 primitive calls) in 0.338 seconds

regex也有一个partial=True标志,但这似乎并不像我们期望的那样工作。

00jrzges

00jrzges2#

不使用正则表达式。

from difflib import SequenceMatcher
SequenceMatcher(None, string, pattern).ratio()
# => 0.7536231884057971

您甚至可以匹配单词而不是字符:

SequenceMatcher(None, string.split(), pattern.split()).ratio()
# => 0.7368421052631579
vtwuwzda

vtwuwzda3#

据我所知,任何正则表达式库都不可能做到这一点,但如果您可以访问状态机并一次遍历一个字符,就可以做到这一点。
将正则表达式编译成状态机有点棘手,但是运行状态机很简单,因此您可以执行任何类型的步进。例如,mine is here
这可以告诉你在多少个字符之后,它从“可能匹配取决于未来的输入”切换到“由于冲突而不匹配”,但不是直接的百分比(尽管我不认为这是你真正想要的)。

相关问题