确定正则表达式的特异性

时间:2010-08-31 18:04:26

标签: c regex pcre

给出以下正则表达式:

 - alice@[a-z]+\.[a-z]+
 - [a-z]+@[a-z]+\.[a-z]+
 - .*

字符串alice@myprovider.com显然会匹配所有三个正则表达式。在我正在开发的应用程序中,我们只对“最具体”的匹配感兴趣。在这种情况下,这显然是第一个 不幸的是,似乎没有办法做到这一点。我们正在使用PCRE,但我没有找到办法做到这一点,在互联网上搜索也没有成果 一种可能的方法是保持正则表达式按降序特异性排序,然后简单地进行第一次匹配。当然接下来的问题是如何对正则表达式数组进行排序。不能向最终用户提供责任以确保对阵列进行排序。 所以我希望你们能在这里帮助我......

谢谢!

4 个答案:

答案 0 :(得分:5)

以下是我根据Donald Miner在Python中实施的研究论文开发的针对MAC地址的规则开发的这个问题的解决方案。

基本上,最具体的匹配来自不是任何其他匹配模式的超集的模式。对于特定问题域,您创建一系列测试(函数),比较两个RE并返回哪个是超集,或者它们是否为正交。这可以让你构建一个匹配树。对于特定的输入字符串,您将浏览根模式并查找任何匹配项。然后浏览他们的子模式。如果在任何时候,正交模式匹配,则会引发错误。

<强>设置

import re

class RegexElement:
    def __init__(self, string,index):
        self.string=string
        self.supersets = []
        self.subsets = []
        self.disjoints = []
        self.intersects = []
        self.maybes = []
        self.precompilation = {}
        self.compiled = re.compile(string,re.IGNORECASE)
        self.index = index

SUPERSET  = object()
SUBSET    = object()
INTERSECT = object()
DISJOINT  = object()
EQUAL     = object()

测试

每个测试需要2个字符串(a和b)并尝试确定它们的相关性。如果测试无法确定关系,则返回None。

SUPERSET表示ab的超集。 b的所有匹配都将与a匹配。

SUBSET表示ba的超集。

INTERSECT表示a的某些匹配项与b匹配,但有些匹配项不匹配,b的某些匹配项不匹配a。< / p>

DISJOINT表示a的匹配项不匹配b

EQUAL表示a的所有匹配都与b匹配,b的所有匹配都将匹配a

    def equal_test(a, b):  
        if a == b: return EQUAL

图表

  class SubsetGraph(object):
    def __init__(self, tests):
        self.regexps = []
        self.tests = tests
        self._dirty = True
        self._roots = None

    @property
    def roots(self):
        if self._dirty:
            r = self._roots = [i for i in self.regexps if not i.supersets]
            return r
        return self._roots

    def add_regex(self, new_regex):
        roots = self.roots
        new_re = RegexElement(new_regex)
        for element in roots:
            self.process(new_re, element)
        self.regexps.append(new_re)

    def process(self, new_re, element):
        relationship = self.compare(new_re, element)
        if relationship:
            getattr(self, 'add_' + relationship)(new_re, element)

    def add_SUPERSET(self, new_re, element):
        for i in element.subsets:
            i.supersets.add(new_re)
            new_re.subsets.add(i)

        element.supersets.add(new_re)
        new_re.subsets.add(element)

    def add_SUBSET(self, new_re, element):
        for i in element.subsets:
            self.process(new_re, i)

        element.subsets.add(new_re)
        new_re.supersets.add(element)

    def add_DISJOINT(self, new_re, element):
        for i in element.subsets:
            i.disjoints.add(new_re)
            new_re.disjoints.add(i)

        new_re.disjoints.add(element)
        element.disjoints.add(new_re)

    def add_INTERSECT(self, new_re, element):
        for i in element.subsets:
            self.process(new_re, i)

        new_re.intersects.add(element)
        element.intersects.add(new_re)

    def add_EQUAL(self, new_re, element):
        new_re.supersets = element.supersets.copy()
        new_re.subsets = element.subsets.copy()
        new_re.disjoints = element.disjoints.copy()
        new_re.intersects = element.intersects.copy()

    def compare(self, a, b):
        for test in self.tests:
            result = test(a.string, b.string)
            if result:
                return result

    def match(self, text, strict=True):
        matches = set()
        self._match(text, self.roots, matches)
        out = []
        for e in matches:
            for s in e.subsets:
                if s in matches:
                    break
            else:
                out.append(e)
        if strict and len(out) > 1:
            for i in out:
                print(i.string)
            raise Exception("Multiple equally specific matches found for " + text)
        return out

    def _match(self, text, elements, matches):
        new_elements = []
        for element in elements:
            m = element.compiled.match(text)
            if m:
                matches.add(element)
                new_elements.extend(element.subsets)
        if new_elements:
            self._match(text, new_elements, matches)

<强>用法

graph = SubsetGraph([equal_test, test_2, test_3, ...])
graph.add_regex("00:11:22:..:..:..")
graph.add_regex("..(:..){5,5}"
graph.match("00:de:ad:be:ef:00")

完整可用的版本为here

答案 1 :(得分:4)

我的直觉表明,这不仅在计算成本和实施难度方面都是一个难题,而且在任何现实的方式下都可能无法解决。考虑以下两个正则表达式来接受字符串alice@myprovider.com

    alice@[a-z]+\.[a-z]+ 
    [a-z]+@myprovider.com

其中哪一个更具体?

答案 2 :(得分:0)

我正在考虑PHP项目路由解析器的类似问题。在阅读了其他答案和评论之后,还考虑了所涉及的成本,我可能会完全朝着另一个方向前进。

然而,解决方案是简单地按正常表达式列表的字符串长度排序。

它并不完美,但只需删除[]组,它就会更接近。在问题的第一个例子中,它将列出:

- alice@[a-z]+\.[a-z]+
- [a-z]+@[a-z]+\.[a-z]+
- .*

对此,删除任何[] -group的内容后:

- alice@+\.+
- +@+\.+
- .*

另一个答案中的第二个例子也是如此,[] -groups被完全删除并按长度排序,这个:

alice@[a-z]+\.[a-z]+ 
[a-z]+@myprovider.com

将排序为:

+@myprovider.com
alice@+\.+ 

如果我选择使用它,这对我来说是一个足够好的解决方案。在排序和在未修改的正则表达式列表中应用排序之前,下行将是删除所有[]组的开销,但是嘿 - 你无法获得所有内容。

答案 3 :(得分:0)

这有点骇人听闻,但它可以为将近10年前提出的这个问题提供实用的解决方案。

如@torak所指出的那样,很难定义一个正则表达式比另一个更具体的含义。

我的建议是查看正则表达式相对于与之匹配的字符串的稳定性如何。研究稳定性的通常方法是对输入进行较小的更改,然后查看是否仍然得到相同的结果。

例如,字符串alice@myprovider.com与正则表达式/alice@myprovider\.com/匹配,但是如果对字符串进行任何更改,则将不匹配。因此,此正则表达式非常不稳定。但是正则表达式/.*/非常稳定,因为您可以对字符串进行任何更改,并且仍然匹配。

因此,在寻找最特定的正则表达式时,我们正在针对与之匹配的字符串寻找最不稳定的正则表达式。

为了实现此测试的稳定性,我们需要定义如何选择与正则表达式匹配的字符串的较小更改。这是另一种蠕虫。例如,我们可以选择将字符串的每个字符更改为随机字符,然后针对正则表达式或其他多种可能的选择进行测试。为简单起见,我建议一次从字符串中删除一个字符,然后进行测试。

因此,如果匹配的字符串长N个字符,则我们需要进行N个测试。让我们看看一次从字符串alice@foo.com中删除一个字符,该字符串与下表中的所有正则表达式匹配。它是12个字符长,所以有12个测试。在下表中,

  • 0表示正则表达式不匹配(不稳定),
  • 1表示匹配(稳定)
              /alice@[a-z]+\.[a-z]+/    /[a-z]+@[a-z]+\.[a-z]+/     /.*/
  
lice@foo.com           0                           1                  1
aice@foo.com           0                           1                  1
alce@foo.com           0                           1                  1
alie@foo.com           0                           1                  1
alic@foo.com           0                           1                  1
alicefoo.com           0                           0                  1
alice@oo.com           1                           1                  1
alice@fo.com           1                           1                  1
alice@fo.com           1                           1                  1
alice@foocom           0                           0                  1 
alice@foo.om           1                           1                  1
alice@foo.cm           1                           1                  1
                      ---                         ---                ---  
total score:           5                          10                 12

得分最低的正则表达式是最具体的。当然,总的来说,具有相同分数的正则表达式可能不止一个,这反映了这样一个事实,即存在一些正则表达式,通过任何合理的衡量特异性的方式,它们之间都是一样的。尽管对于正则表达式而言,它也可能产生相同的分数,但人们可以轻易辩称,彼此之间的区别并不那么具体(如果您能想到一个示例,请发表评论)。

但是回到@torak提出的问题,其中哪个更具体:

alice@[a-z]+\.[a-z]+ 
[a-z]+@myprovider.com

我们可以争辩说第二种方法更具体,因为它约束了更多的字符,并且上面的测试也符合该观点。

正如我所说,我们选择对匹配多个正则表达式的字符串进行细微更改的方法是一罐蠕虫,而上述方法产生的答案可能取决于该选择。但是正如我所说,这是一个易于实现的骇客-并不严格。

当然,如果匹配的字符串为空,该方法也会中断。如果测试随着字符串长度的增加而增加,则很有用。如果字符串很短,则针对其特异性明显不同的正则表达式更可能产生均等的分数。