找出两个 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
=> *d
。 set 与 star 之后的 d
匹配,因此左侧仍有标记,而右侧将是完整的。然而,这两个模式在ad
、bd
、cd
和dd
上相交,因此需要修复。我的几乎 O(N) 答案被抛出。
词法分析器
词法分析过程很简单,除了处理转义字符并删除多余的星号。令牌分为 sets、stars、wild 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 模式(或正则表达式)的匹配项是不是相交的算法的主要内容,如果未能解决你的问题,请参考以下文章