You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

正则表达式中*循环的回溯机制原理及代码实现问题

正则表达式中*循环的回溯机制原理及代码实现问题

嘿,我看你在跟着Damian Conway的正则思路手动实现X*的回溯逻辑,还碰到了个棘手的小问题,咱们来一步步理清楚!

首先先回顾下你的背景:你参考了Damian把/X*/(0次或多次匹配X)转化的伪代码,自己写了JS版本来实现/X*i/的匹配逻辑,测试字符串比如deXXXixia ho或者deXXXoxia ho,但遇到了一个矛盾点——如果不在finally块里加matchPos++,程序在没有初始匹配的时候根本没法推进字符串;可加了之后,就算匹配失败,matchPos也会被强制自增,完全不符合预期。

先说说你代码里的核心问题

  1. 外层循环逻辑混淆:你外层用while( matchPos <= str.length )直接操作matchPos,但其实我们需要的是从每个独立的起始位置尝试匹配,而不是让matchPos一直往后走。这样的逻辑会导致回溯过程中位置指针被破坏,没法正确回到之前的状态。
  2. finally块的matchPos++完全错误:finally不管匹配成功失败都会执行,这会导致哪怕还在当前起始位置尝试回溯匹配,matchPos也会被强制加1,直接跳过了后续的回溯尝试,逻辑完全乱掉。
  3. 缺少位置重置逻辑:Damian的伪代码里,每次回溯尝试不同数量的X时,都是从当前起始位置重新开始匹配的,但你的代码没有保存初始起始位置,导致回溯时没法回到正确的起点,匹配数量的计算也出错了。

修正后的实现思路

核心要把「尝试从当前起始位置匹配」和「移动到下一个起始位置」彻底分开:

  • 外层用for循环遍历每个可能的起始索引,每个起始位置的匹配尝试都是独立的。
  • 针对每个起始位置,单独维护一个currentPos指针,用来处理X*的匹配和回溯,不会影响外层的起始索引。
  • 严格遵循Damian的回溯逻辑:先贪婪匹配最多的X,尝试匹配后续的i;失败就减少X的匹配数量,回到起始位置重新匹配,直到所有可能的数量都试过,再换下一个起始位置。

修正后的代码

const Backtracking = new Error("Backtracking exception");

function executeRegex(str, startPos) {
    // 遍历每一个可能的起始匹配位置
    for (let start = startPos; start < str.length; start++) {
        let maxReps = Infinity;
        while (true) {
            let currentPos = start; // 每次回溯尝试都从当前起始位置重新开始
            let repCount = 0;

            // 匹配最多maxReps个X,遇到非X或字符串结尾就停止
            for (let j = 0; j < maxReps; j++) {
                if (currentPos >= str.length || str[currentPos] !== 'X') {
                    break;
                }
                currentPos++;
                repCount++;
            }

            try {
                // 尝试匹配后续的'i'
                if (currentPos >= str.length || str[currentPos] !== 'i') {
                    throw Backtracking;
                }
                // 匹配成功,直接返回true
                return true;
            } catch (e) {
                if (e !== Backtracking) {
                    throw e; // 只处理我们定义的回溯异常
                }
                // 回溯:减少最多匹配的X数量
                maxReps = repCount - 1;
                if (maxReps < 0) {
                    // 当前起始位置所有可能的X数量都试过了,换下一个起始位置
                    break;
                }
                // 继续循环,尝试少匹配一个X
            }
        }
    }
    // 所有起始位置都尝试过,匹配失败
    return false;
}

console.log(executeRegex('deXXXoxia ho', 0)); // 输出false(XXX后是o不是i)
console.log(executeRegex('deXXXixia ho', 0)); // 输出true(XXX后是i)

关键修正点解释

  • 外层for循环负责遍历每个起始位置,确保每个位置的匹配尝试互不干扰。
  • 每次进入回溯循环时,都把currentPos重置为当前起始位置start,这样每次尝试不同数量的X时,都是从起点重新匹配,不会继承上一次的位置。
  • 去掉了多余的finally块,匹配成功直接返回,失败就调整maxReps继续尝试,直到所有可能都耗尽再换下一个起始位置。
  • 增加了字符串边界判断,避免访问超出字符串长度的位置导致undefined错误。

现在你再测试的话,不管是有匹配还是没匹配的情况,逻辑都能正常运行了,也不会出现位置指针乱跳的问题~

备注:内容来源于stack exchange,提问作者rafmartom

火山引擎 最新活动