部分正则表达式匹配

时间:2016-03-22 02:12:09

标签: python regex

我正在询问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

虽然我们都可以看到模式的第一部分与字符串的第一部分匹配。

因此,有没有办法查看字符串中匹配模式的百分比,而不是搜索字符串中的整个模式?

3 个答案:

答案 0 :(得分:4)

不使用正则表达式。

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

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

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

答案 1 :(得分:4)

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

我正在研究部分匹配的想法,并在搜索过程中找到了这个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

性能比没有partial_match()标志的regex.ENHANCEMATCH解决方案要好一些。但是带有标志的速度较慢。

带有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标志,但这似乎根本没有我们期望的那样。

答案 2 :(得分:0)

我所知道的任何正则表达式库都无法实现,但如果您可以访问状态机并一次单步执行一个字符,则可以实现。

将正则表达式编译到状态机有点棘手,但运行状态机是微不足道的,所以你可以做任何你想要的步进。例如,mine is here

这可以告诉你它切换了多少个字符&#34;可能会根据未来的输入而匹配&#34;由于冲突而与#34;不匹配,&#34;但不是直接的百分比(虽然我不认为这真的是你想要的)。