找出两个 Glob 模式(或正则表达式)的匹配项是不是相交的算法

Posted

技术标签:

【中文标题】找出两个 Glob 模式(或正则表达式)的匹配项是不是相交的算法【英文标题】:Algorithm to find out whether the matches for two Glob patterns (or Regular Expressions) intersect找出两个 Glob 模式(或正则表达式)的匹配项是否相交的算法 【发布时间】:2013-09-12 19:03:36 【问题描述】:

我正在寻找与 Redis KEYS command accepts 类似的匹配 glob 样式模式。引用:

h?llo 匹配 hello、hallo 和 hxllo h*llo 匹配 hllo 和 heeeello h[ae]llo 匹配 hello 和 halo,但不匹配 hillo

但我不是针对文本字符串进行匹配,而是将模式与另一个模式匹配,所有运算符在两端都有意义。

例如,这些模式应该在同一行中相互匹配:

prefix*       prefix:extended*
*suffix       *:extended:suffix
left*right    left*middle*right
a*b*c         a*b*d*b*c
hello*        *ok
pre[ab]fix*   pre[bc]fix*

这些不应该匹配:

prefix*       wrong:prefix:*
*suffix       *suffix:wrong
left*right    right*middle*left
pre[ab]fix*   pre[xy]fix*
?*b*?         bcb

所以我想知道......

如果有可能(实施验证算法),如果有的话? 如果不可能,那么哪个正则表达式子集是可能的? (即不允许 * 通配符?) 如果确实有可能,什么是高效算法? 所需的时间复杂度是多少?

编辑: 找到 this other question on RegEx subset 但这与 hello**ok 匹配的词不完全相同,它们不是彼此的子集/超集,但它们确实相交。

所以我猜在数学上,这可能被表述为;是否可以确定性地检查一个模式匹配的一组单词,与另一个模式匹配的一组单词相交,导致一个非空集?


编辑:朋友@neizod 绘制了这张消除表,它巧妙地可视化了可能的/部分解决方案:Elimination rule


编辑:将为那些还可以提供工作代码(任何语言)和证明它的测试用例的人增加额外的奖励。


编辑:添加了 ?*b*? @DanielGimenez 在 cmets 中发现的测试用例。

【问题讨论】:

据我了解这个问题,您问是否有办法确定模式 A 是否匹配另一个模式 B 匹配的单词的超集或子集。对吗? @jpmc26 是的,没错。 当然不在我的联盟中,但看起来很简单:***.com/a/6364335/1394393。关于“扩展”的部分可能是指环顾四周和区分大小写之类的修饰符,但听起来您并不需要这些。 另外,我不认为hello**ok 有那种关系。第一个匹配hello world,但第二个不匹配。第二个匹配grok,但第一个不匹配。因此,虽然它们确实有交集,但它们都不是另一个的子集。 Possibly related? 【参考方案1】:

现在见证这个全副武装的作战站的火力!

(我在这个答案上工作了太多,我的大脑已经坏了;应该有一个徽章。)

为了确定两个模式是否相交,我创建了一个递归回溯解析器——当遇到 Kleene stars 时,会创建一个新堆栈,以便在失败时创建未来一切都会回滚,star 会消耗下一个字符。

您可以查看此答案的历史,以确定这一切是如何得出的以及为什么需要这样做,但基本上仅通过向前看一个标记来确定交叉点是不够的,这就是我之前所做的。

这种情况打破了旧答案[abcd]d => *dsetstar 之后的 d 匹配,因此左侧仍有标记,而右侧将是完整的。然而,这两个模式在adbdcddd 上相交,因此需要修复。我的几乎 O(N) 答案被抛出。

词法分析器

词法分析过程很简单,除了处理转义字符并删除多余的星号。令牌分为 setsstarswild character (?) em> 和 字符。这与我以前的版本不同,其中一个标记是一串字符而不是单个字符。随着越来越多的案例出现,将字符串作为标记更多的是障碍而不是优势。

解析器

解析器的大部分功能都很简单。给定左侧类型的开关调用一个函数,该函数是一个开关,该函数确定适当的函数以将其与右侧的类型进行比较。比较的结果将两个开关冒泡到原始被调用者,通常是解析器的主循环。

解析星星

简单以结束。当遇到这种情况时,它会接管一切。首先,它将自己一方的下一个令牌与另一方的令牌进行比较,推进另一方直到找到匹配项。

一旦找到匹配项,它就会检查是否所有内容都匹配到两个模式的末尾。如果是这样,则模式相交。否则,它会将另一方的下一个令牌从与之比较的原始令牌推进,并重复该过程。

当遇到两个 anys 时,然后从彼此的下一个令牌开始进入他们自己的替代分支。

function intersects(left, right) 
    var lt, rt,
        result = new CompareResult(null, null, true);

    lt = (!left || left instanceof Token) ? left : tokenize(left);
    rt = (!right || right instanceof Token) ? right : tokenize(right);

    while (result.isGood && (lt || rt)) 
        result = tokensCompare(lt, rt);

        lt = result.leftNext;
        rt = result.rightNext;
    

    return result;


function tokensCompare(lt, rt) 
    if (!lt && rt) return tokensCompare(rt, lt).swapTokens();

    switch (lt.type) 
        case TokenType.Char: return charCompare(lt, rt);
        case TokenType.Single: return singleCompare(lt, rt);
        case TokenType.Set: return setCompare(lt, rt);
        case TokenType.AnyString: return anyCompare(lt, rt);
    


function anyCompare(tAny, tOther) 
    if (!tOther) return new CompareResult(tAny.next, null);

    var result = CompareResult.BadResult;

    while (tOther && !result.isGood) 
        while (tOther && !result.isGood) 
            switch (tOther.type) 
                case TokenType.Char: result = charCompare(tOther, tAny.next).swapTokens(); break;
                case TokenType.Single: result = singleCompare(tOther, tAny.next).swapTokens(); break;
                case TokenType.Set: result = setCompare(tOther, tAny.next).swapTokens(); break;
                case TokenType.AnyString:
                    // the anyCompare from the intersects will take over the processing.
                    result = intersects(tAny, tOther.next);
                    if (result.isGood) return result;
                    return intersects(tOther, tAny.next).swapTokens();
            

            if (!result.isGood) tOther = tOther.next;
        

        if (result.isGood) 
            // we've found a starting point, but now we want to make sure this will always work.
            result = intersects(result.leftNext, result.rightNext);
            if (!result.isGood) tOther = tOther.next;
        
    

    // If we never got a good result that means we've eaten everything.
    if (!result.isGood) result = new CompareResult(tAny.next, null, true);

    return result;


function charCompare(tChar, tOther) 
    if (!tOther) return CompareResult.BadResult;

    switch (tOther.type) 
        case TokenType.Char: return charCharCompare(tChar, tOther); 
        case TokenType.Single: return new CompareResult(tChar.next, tOther.next);
        case TokenType.Set: return setCharCompare(tOther, tChar).swapTokens(); 
        case TokenType.AnyString: return anyCompare(tOther, tChar).swapTokens();
    


function singleCompare(tSingle, tOther) 
    if (!tOther) return CompareResult.BadResult;

    switch (tOther.type) 
        case TokenType.Char: return new CompareResult(tSingle.next, tOther.next);
        case TokenType.Single: return new CompareResult(tSingle.next, tOther.next);
        case TokenType.Set: return new CompareResult(tSingle.next, tOther.next);
        case TokenType.AnyString: return anyCompare(tOther, tSingle).swapTokens();
    

function setCompare(tSet, tOther) 
    if (!tOther) return CompareResult.BadResult;

    switch (tOther.type) 
        case TokenType.Char: return setCharCompare(tSet, tOther);
        case TokenType.Single: return new CompareResult(tSet.next, tOther.next);
        case TokenType.Set: return setSetCompare(tSet, tOther);
        case TokenType.AnyString: return anyCompare(tOther, tSet).swapTokens();
    


function anySingleCompare(tAny, tSingle) 
    var nextResult = (tAny.next) ? singleCompare(tSingle, tAny.next).swapTokens() :
        new CompareResult(tAny, tSingle.next);
    return (nextResult.isGood) ? nextResult: new CompareResult(tAny, tSingle.next);


function anyCharCompare(tAny, tChar) 
    var nextResult = (tAny.next) ? charCompare(tChar, tAny.next).swapTokens() :
        new CompareResult(tAny, tChar.next);

    return (nextResult.isGood) ? nextResult : new CompareResult(tAny, tChar.next);


function charCharCompare(litA, litB) 
    return (litA.val === litB.val) ?
        new CompareResult(litA.next, litB.next) : CompareResult.BadResult;


function setCharCompare(tSet, tChar) 
    return (tSet.val.indexOf(tChar.val) > -1) ?
        new CompareResult(tSet.next, tChar.next) : CompareResult.BadResult;


function setSetCompare(tSetA, tSetB) 
    var setA = tSetA.val,
        setB = tSetB.val;

    for (var i = 0, il = setA.length; i < il; i++) 
        if (setB.indexOf(setA.charAt(i)) > -1) return new CompareResult(tSetA.next, tSetB.next);
    
    return CompareResult.BadResult;

jsFiddle

时间复杂度

任何带有“递归回溯”字样的东西至少是 O(N2)。

可维护性和可读性

我故意用一个单一的开关将任何分支分解成自己的功能。当一个字符串就足够时,我还使用命名常量。这样做会使代码更长更冗长,但我认为它更容易理解。

测试

您可以在 Fiddle 中查看所有测试。您可以在 Fiddle 输出中查看 cmets 以了解它们的用途。每种令牌类型都针对每种令牌类型进行了测试,但我还没有在一次测试中尝试过所有可能的比较。我还想出了一些随机的困难的,比如下面的。

abc[def]?fghi?*nop*[tuv]uv[wxy]?yz => a?[cde]defg*?ilmn[opq]*tu*[xyz]*

如果有人想自己测试一下,我在 jsFiddle 上添加了一个接口。一旦我添加了递归,日志记录就会被破坏。

我认为我尝试的负面测试不够多,尤其是在我创建的最后一个版本中。

优化

目前解决方案是蛮力解决方案,但足以处理任何情况。我想在某个时候回到这一点,通过一些简单的优化来提高时间复杂度。

在开始时进行检查以减少比较可能会增加某些常见情况的处理时间。例如,如果一个模式以 star 开头,一个以一个结尾,那么我们已经知道它们会相交。我还可以检查模式开头和结尾的所有字符,如果两个模式都匹配,则将其删除。这样他们就被排除在任何未来的递归之外。

致谢

我最初使用 @m.buettner 的 测试来测试我的代码,然后才提出自己的代码。我还浏览了他的代码以帮助我更好地理解问题。

【讨论】:

我一直想看到有人为模式匹配问题提出一个合适的解析器。 =) +1(希望可以更多。) 这太疯狂了。非常强大,感谢您为此付出的辛勤工作。 如果我想更新此代码以处理以下情况,最好的方法是什么? "hello.one.world" 匹配 "hello.(one|two).world" 也匹配 "hello.one.world" 匹配 "hello.one|two.world" 谢谢! @KirkOuimet,这本身可能是一个 SO 问题。我将从创建用于交替、范围打开/关闭和转义字符的令牌类型开始。轮换最会破坏事情。如果匹配失败,那么解析器将不得不继续向前看以查看替换字符是否存在,然后再次开始检查范围的开始位置。创建范围可能很简单,它们只是找到“(”的索引。如果找到“)”并且没有范围,那么只需将其视为文字。将交替与交替进行比较将是最困难的情况。祝你好运。【参考方案2】:

使用您非常简化的模式语言,您的问题中的 pastebin 链接和 jpmc26 的 cmets 几乎一直存在:主要问题是,您的输入字符串的文字左端和右端是否匹配。如果是这样,并且两者都包含至少一个*,则字符串匹配(因为您始终可以将其他字符串中间文字文本与该星号匹配)。有一种特殊情况:如果其中只有一个为空(删除前后缀后),如果另一个完全由*s 组成,它们仍然可以匹配。

当然,在检查字符串的结尾是否匹配时,还需要考虑单字符通配符? 和字符类。单字符通配符很简单:它不会失败,因为它总是匹配另一个字符。如果是一个字符类,而另一个只是一个字符,则需要检查该字符是否在该类中。如果它们都是类,则需要检查类的交集(这是一个简单的集合交集)。

以下是 javascript 中的所有内容(查看代码 cmets 以了解我上面概述的算法如何映射到代码):

var trueInput = [
     left: 'prefix*', right: 'prefix:extended*' ,
     left: '*suffix', right: '*:extended:suffix' ,
     left: 'left*right', right: 'left*middle*right' ,
     left: 'a*b*c', right: 'a*b*d*b*c' ,
     left: 'hello*', right: '*ok' ,
     left: '*', right: '*',
     left: '*', right: '**',
     left: '*', right: '',
     left: '', right: '',
     left: 'abc', right: 'a*c',
     left: 'a*c', right: 'a*c',
     left: 'a[bc]d', right: 'acd',
     left: 'a[bc]d', right: 'a[ce]d',
     left: 'a?d', right: 'acd',
     left: 'a[bc]d*wyz', right: 'abd*w[xy]z',
];

var falseInput = [
     left: 'prefix*', right: 'wrong:prefix:*' ,
     left: '*suffix', right: '*suffix:wrong' ,
     left: 'left*right', right: 'right*middle*left' ,
     left: 'abc', right: 'abcde',
     left: 'abcde', right: 'abc',
     left: 'a[bc]d', right: 'aed',
     left: 'a[bc]d', right: 'a[fe]d',
     left: 'a?e', right: 'acd',
     left: 'a[bc]d*wyz', right: 'abc*w[ab]z',
];

// Expects either a single-character string (for literal strings
// and single-character wildcards) or an array (for character
// classes).
var characterIntersect = function(a,b) 
    // If one is a wildcard, there is an intersection.
    if (a === '?' || b === '?')
        return true;

    // If both are characters, they must be the same.
    if (typeof a === 'string' && typeof b === 'string')
        return a === b;

    // If one is a character class, we check that the other
    // is contained in the class.
    if (a instanceof Array && typeof b === 'string')
        return (a.indexOf(b) > -1);
    if (b instanceof Array && typeof a === 'string')
        return (b.indexOf(a) > -1);

    // Now both have to be arrays, so we need to check whether
    // they intersect.
    return a.filter(function(character) 
        return (b.indexOf(character) > -1);
    ).length > 0;
;

var patternIntersect = function(a,b) 
    // Turn the strings into character arrays because they are
    // easier to deal with.
    a = a.split("");
    b = b.split("");

    // Check the beginnings of the string (up until the first *
    // in either of them).
    while (a.length && b.length && a[0] !== '*' && b[0] !== '*')
    
        // Remove the first character from each. If it's a [,
        // extract an array of all characters in the class.
        aChar = a.shift();
        if (aChar == '[')
        
            aChar = a.splice(0, a.indexOf(']'));
            a.shift(); // remove the ]
        
        bChar = b.shift();
        if (bChar == '[')
        
            bChar = b.splice(0, b.indexOf(']'));
            b.shift(); // remove the ]
        

        // Check if the two characters or classes overlap.
        if (!characterIntersect(aChar, bChar))
            return false;
    

    // Same thing, but for the end of the string.
    while (a.length && b.length && a[a.length-1] !== '*' && b[b.length-1] !== '*')
    
        aChar = a.pop();
        if (aChar == ']')
        
            aChar = a.splice(a.indexOf('[')+1, Number.MAX_VALUE);
            a.pop(); // remove the [
        
        bChar = b.pop();
        if (bChar == ']')
        
            bChar = b.splice(b.indexOf('[')+1, Number.MAX_VALUE);
            b.pop(); // remove the [
        

        if (!characterIntersect(aChar, bChar))
            return false;
    

    // If one string is empty, the other has to be empty, too, or
    // consist only of stars.
    if (!a.length && /[^*]/.test(b.join('')) ||
        !b.length && /[^*]/.test(b.join('')))
        return false;

    // The only case not covered above is that both strings contain
    // a * in which case they certainly overlap.
    return true;
;

console.log('Should be all true:');
console.log(trueInput.map(function(pair) 
    return patternIntersect(pair.left, pair.right);
));

console.log('Should be all false:');
console.log(falseInput.map(function(pair) 
    return patternIntersect(pair.left, pair.right);
));

这不是最简洁的实现,但它可以工作并且(希望)仍然非常可读。检查开头和结尾有相当多的代码重复(在检查开头之后可以通过简单的reverse 来缓解 - 但我认为这只会使事情变得模糊)。而且可能还有很多其他方面可以大大改进,但我认为逻辑已经到位。

还有几点说明:实现假定模式格式正确(没有不匹配的左括号或右括号)。另外,我从this answer 中获取了数组交集代码,因为它很紧凑——如果需要,你当然可以提高它的效率。

不管那些实现细节,我想我也可以回答你的复杂性问题:外循环同时遍历两个字符串,一次一个字符。这就是线性复杂度。循环中的所有内容都可以在恒定时间内完成,除了字符类测试。如果一个字符是字符类而另一个不是,则需要线性时间(以类的大小为参数)来检查字符是否在类中。但这并不能使它成为二次方,因为类中的每个字符都意味着外循环的迭代次数减少了一次。所以这仍然是线性的。因此,最昂贵的事情是两个字符类的交集。这可能比线性时间更复杂,但最糟糕的是O(N log N):毕竟,您可以对两个字符类进行排序,然后在线性时间中找到一个交集。我认为您甚至可以通过将字符类中的字符散列到它们的 Unicode 代码点(JS 中的 .charCodeAt(0))或其他数字来获得整体线性时间复杂度 - 并且可以在散列集中找到交集线性时间。所以,如果你真的想要,我认为你应该能够访问O(N)

N 是什么?上限是两种模式的长度之和,但在大多数情况下实际上会更短(取决于两种模式的前缀和后缀的长度)。

请指出我的算法丢失的任何边缘情况。我也很高兴提出改进建议,如果它们改进或至少不会降低代码的清晰度。

Here is a live demo on JSBin(感谢 chakrit 将其粘贴在那里)。


编辑: 正如 Daniel 指出的那样,我的算法遗漏了一个概念性的边缘情况。如果(在消除开头和结尾之前或之后)一个字符串不包含 * 而另一个包含,则在某些情况下,两者仍然会发生冲突。不幸的是,我现在没有时间调整我的代码 sn-p 以适应该问题,但我可以概述如何解决它。

消除字符串的两端后,如果两个字符串都为空或都包含至少*,它们将始终匹配(通过完全消除后可能的*-distributions 看到这一点)。唯一不重要的情况是一个字符串仍然包含*,但另一个不包含(无论是否为空)。我们现在需要做的是再次从左到右走两个字符串。让我称包含*A的字符串和不包含*B的字符串。

我们从左到右遍历 A,跳过所有 *(只注意 ?、字符类和文字字符)。对于每个相关的标记,我们从左到右检查它是否可以在 B 中匹配(在第一次出现时停止)并将 B 光标推进到该位置。如果我们在 A 中找到一个在 B 中找不到的标记,它们就不匹配。如果我们设法为 A 中的每个标记找到匹配项,它们确实匹配。这样,我们仍然使用线性时间,因为不涉及回溯。这里有两个例子。这两个应该匹配:

A: *a*[bc]*?*d* --- B: db?bdfdc
    ^                    ^
A: *a*[bc]*?*d* --- B: db?bdfdc
      ^                   ^
A: *a*[bc]*?*d* --- B: db?bdfdc
           ^               ^
A: *a*[bc]*?*d* --- B: db?bdfdc
             ^               ^

这两个不应该匹配:

A: *a*[bc]*?*d* --- B: dbabdfc
    ^                    ^
A: *a*[bc]*?*d* --- B: dbabdfc
      ^                   ^
A: *a*[bc]*?*d* --- B: dbabdfc
           ^               ^
A: *a*[bc]*?*d* --- B: dbabdfc
             !               

它失败了,因为? 不可能在第二个d 之前匹配,之后B 中没有更多的d 来容纳A 中的最后一个d

如果我花时间将字符串正确解析为令牌对象,这可能很容易添加到我当前的实现中。但是现在,我不得不再次麻烦地解析这些字符类。我希望这个添加的书面大纲能提供足够的帮助。

PS:当然,我的实现也没有考虑转义元字符,并且可能会在字符类中阻塞 *

【讨论】:

哇,感谢您的广泛回复!我冒昧地对你的代码进行了 jsbin 编辑:jsbin.com/oVACiXe/3/edit?js,console .. 将在一天结束时回来阅读。 在重新阅读您的答案后......并再次查看该 pastebin 链接。我想知道这是否可以递归地完成,这可能更容易/更清晰和可读。 (但是是的,在 JS 的情况下它的性能可能会降低) @DanielGimenez 很好地找到了该测试用例! @DanielGimenez 哦,这很好。我认为这甚至可能是我假设中的一个概念缺陷。稍后我会仔细研究一下。 @chakrit 我发现了我的算法中的缺陷,并添加了关于如何正确处理这些情况的解释。它可以在消除字符串的两端后在另一个线性时间步骤中完成。不幸的是,我没有时间将它实际添加到我的实现中,但我已经写了一个(希望)详尽的描述来说明如何做到这一点。【参考方案3】:

这些特殊模式远不如完整的正则表达式强大,但我会指出,即使使用一般的正则表达式,它可以做你想做的事情。这些必须是“真正的”正则表达式,即仅使用 Kleene 星号、交替( | 操作)以及与任何固定字母表加上空字符串和空集的连接。当然,您也可以在这些操作上使用任何语法糖:一个或多个 (+)、可选 (?)。字符集只是一种特殊的交替 [a-c] == a|b|c。

该算法原则上很简单:使用标准构造将每个正则表达式转换为 DFA:Thompson 后跟 powerset。然后使用叉积构造计算两个原件的交集 DFA。最后检查这个交集 DFA 以确定它是否至少接受一个字符串。这只是从开始状态的一个dfs,看看是否可以达到接受状态。

如果您不熟悉这些算法,很容易找到 Internet 参考资料。

如果交集 DFA 至少接受一个字符串,则原始正则表达式之间存在匹配,并且 dfs 发现的路径给出了一个同时满足两者的字符串。否则不匹配。

【讨论】:

我对自动机理论一无所知,有什么办法可以添加一点伪代码之类的吗?这个问题解决起来非常有趣,我想我明白了,但我认为我无法解决更复杂的问题。感谢您的帮助。 交叉口 DFA。我想我得查一下。是的,一些伪代码会很好。 @chakrit 很遗憾,我现在没有时间做几个小时的工作。这些算法在概念上并不难,但非常详细。 github.com/bniemczyk/automata 有一些部分但不是全部。源码有详细注释。【参考方案4】:

好问题!

这里的主要复杂性是处理字符类 ([...])。我的方法是用正则表达式替换每一个的指定字符。所以对于[xyz],这将是:([xyz?]|\[[^\]]*[xyz].*?\]) - 见下文:

然后对于“前缀”(第一个 * 之前的所有内容),将 ^ 放在开头或对于“后缀”(最后一个 * 之后的所有内容),将 $ 放在末尾。

更多细节:-

    还需要将? 的所有实例替换为(\[.*?\]|[^\\]]),以使其匹配字符类或单个字符(不包括左方括号)。 还需要替换不在字符类中且不是? 的每个单独字符,以使其匹配同一字符、? 或包含该字符的字符类。例如。 a 将变为 ([a?]|\[[^\]]*a.*?\])。 (有点啰嗦,但后来证明是必要的 - 请参阅下面的 cmets。 测试应按以下两种方式进行:测试 prefix #1 转换为 regex 与前缀 #2 然后测试 prefix #2 转换为 regex 与前缀 # 1.如果任一匹配,则可以说前缀“相交”。 对后缀重复步骤 (3.):要获得肯定的结果, 前缀和后缀必须相交。

编辑:除上述情况外,还有一种特殊情况,即其中一个模式至少包含一个 * 而另一个不包含。在这种情况下,应将带有* 的整个模式转换为正则表达式:* 应匹配任何内容,但条件是它只包含 整个 字符类。这可以通过将所有* 实例替换为(\[.*?\]|[^\\]]) 来完成。

为了避免这个答案变得庞大,我不会发布完整的代码,但这里有一个带有单元测试的工作演示:http://jsfiddle.net/mb3Hn/4/

编辑 #2 - 已知不完整: 在当前形式中,演示不支持转义字符(例如 \[)。这不是一个很好的借口,但我只是在当天晚些时候才注意到这些 - 问题中没有提到它们,只有 link。为了处理它们,需要一些额外的正则表达式复杂性,例如在[ 之前检查是否存在反斜杠。使用negative lookbehind 应该很轻松,但不幸的是 Javascript 不支持它。有一些解决方法,例如使用负前瞻来反转字符串和正则表达式,但我不热衷于使用这种额外的复杂性降低代码的可读性,并且不确定它对 OP 的重要性,因此将其保留为“练习读者”。回想起来,也许应该选择一种支持更全面的正则表达式的语言!

【讨论】:

查看我为@m.buettner 发布的内容。这更有帮助,因为我浏览了比赛应该是什么。 再次感谢严格的测试。当没有通配符修复上面评论中的示例时,发现前缀/后缀的错误。但是,您为 m.buettner 发布的帖子揭示了一个更严重的缺陷。在其最简单的形式中,a??b 匹配返回 false。会考虑如何解决这个问题,并希望明天能回来...... @DanielGimenez 好吧,这让事情变得更复杂了!现在已经更新了答案并摆弄了额外的用例 - 现在可以解决我能想到的所有问题,但如果您发现其他任何内容,请告诉我...... 另一个给你的?*b*? => bcb 不正确。您是否将? 视为对零长度有效? ?=b *=c b=b*? 无与伦比。我认为? 需要匹配。 好地方!当其中一个模式包含一个或多个 *s 但另一个不包含时,看起来这是一种特殊情况。已经编辑了我的答案和小提琴 + 包括这些作为额外的单元测试。【参考方案5】:

使用greenery确定一个正则表达式是否匹配另一个正则表达式的子集:

首先,pip3 install https://github.com/ferno/greenery/archive/master.zip

然后:

from greenery.lego import parse as p
a_z = p("[a-z]")
b_x = p("[b-x]")
assert a_z | b_x == a_z
m_n = p("m|n")
zero_nine = p("[0-9]")
assert not m_n | zero_nine == m_n

【讨论】:

以上是关于找出两个 Glob 模式(或正则表达式)的匹配项是不是相交的算法的主要内容,如果未能解决你的问题,请参考以下文章

glob通配符

perl模糊匹配文件名

node中glob模块总结

python文件操作glob_os_等对比

glob/globfree--找出匹配模式的路径名

如何使用正则表达式 (glob) 搜索文件树