JavaScript中如何判断两个含通配符的正则表达式是否匹配
问题根因
原有实现的逻辑存在本质缺陷:双向调用.test()是拿一个模式的字面字符串去匹配另一个模式,仅能覆盖「一个模式完全包含另一个模式」的场景,无法识别两个模式存在交叉公共匹配串、但互相不能匹配对方模式字面量的情况。
以失败用例为例:
john:alex:*转成正则后匹配所有以john:alex:开头的字符串john:*:doe转成正则后匹配所有以john:开头、以:doe结尾的字符串
两个模式存在大量公共匹配串(比如john:alex:doe),按预期应该返回true,但原有逻辑测试时会把模式里的*当做普通字符处理,两次测试都返回false,最终得到错误结果。
另外原有toRegex方法存在转义bug:替换特殊字符时的\\$&是HTML转义残留,正确写法应为\\$&,否则会在转义后的特殊字符后多余拼接amp字符串。
改进实现
判断两个带通配符的模式是否匹配,核心是判断是否存在至少一个字符串能同时被两个模式匹配,也就是求两个模式的匹配集合是否存在交集。针对通用通配符规则(*匹配任意长度任意字符、?匹配单个任意字符),用动态规划直接做两个模式的交集判断,比正则互测覆盖场景更全,代码如下:
/** * 判断两个通配符模式是否存在匹配交集 * @param {string} p 第一个通配符模式 * @param {string} q 第二个通配符模式 * @returns {boolean} 存在公共匹配返回true,否则返回false */ const compare = (p, q) => { const m = p.length, n = q.length; // dp[i][j] 表示p的前i个字符、q的前j个字符是否存在公共匹配串 const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(false)); // 两个空串天然匹配 dp[0][0] = true; // 处理模式开头为连续*的边界情况 for (let i = 1; i <= m; i++) { if (p[i - 1] === '*') dp[i][0] = dp[i - 1][0]; } for (let j = 1; j <= n; j++) { if (q[j - 1] === '*') dp[0][j] = dp[0][j - 1]; } for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { const pc = p[i - 1], qc = q[j - 1]; if (pc === qc || pc === '?' || qc === '?') { // 当前字符相等或为单字符通配符,结果继承前一位的匹配状态 dp[i][j] = dp[i - 1][j - 1]; } else if (pc === '*') { // p侧为多字符通配符:要么*匹配空串,要么*吃掉q侧当前字符 dp[i][j] = dp[i - 1][j] || dp[i][j - 1]; } else if (qc === '*') { // q侧为多字符通配符:要么*匹配空串,要么*吃掉p侧当前字符 dp[i][j] = dp[i][j - 1] || dp[i - 1][j]; } else { // 当前字符都是普通字符且不相等,无匹配可能 dp[i][j] = false; } } } return dp[m][n]; };
效果验证
// 原有正常用例 const a1 = 'john:alex:*'; const b1 = 'john:*'; console.log(compare(a1, b1)); // true // 原有失败用例 const a2 = 'john:alex:*'; const b2 = 'john:*:doe'; console.log(compare(a2, b2)); // true // 其他边界用例 console.log(compare('a*c', '*b*')); // true 公共串abc console.log(compare('a*b', 'c*d')); // false 无公共串 console.log(compare('*.txt', 'a*')); // true 公共串a.txt console.log(compare('a?c', 'a*')); // true 公共串abc、adc等
如果需要保留通配符转正则的能力,修复转义bug后的toRegex方法如下:
const toRegex = (str) => new RegExp( '^' + str .replace(/[\-\[\]\/\{\}\(\)\+\.\\\^\$\|]/g, '\\$&') .replace(/\*/g, '.*') .replace(/\?/g, '.') + '$', );
内容的提问来源于stack exchange,提问作者sumanth
相关产品推荐
相关产品推荐

