正则表达式负面环顾两个相邻的比赛

时间:2013-06-27 10:41:13

标签: javascript regex

来自那里的人应该是一个简单的问题:

如果我运行此JavaScript:

var regex = new RegExp("(?!cat)dog(?!cat)","g");
var text =  "catdogcat catdogdog catdogdogcat".replace(regex,"000");
console.log(text);

输出:

catdogcat cat000000 cat000dogcat

但是我应该输出这个:

catdogcat cat000000 cat000000cat

为什么catdogdogcat中的第二只“狗”没有被000取代?

编辑:只要两只猫没有猫,我想替换“狗”。在catdogdogcat中,两只狗都满足了这个要求,所以应该更换它们。显然我不明白这些消极的看法...

2 个答案:

答案 0 :(得分:7)

您的方法存在两个问题。

  1. 您的第一个前瞻需要是一个后视。当您编写(?!cat)时,引擎会检查接下来的三个字符是否为cat,然后重置到它开始的位置(这就是看起来前进的方式),然后尝试将dog与这三个相同的字符进行匹配。因此,前瞻不会添加任何内容:如果您可以匹配dog,则显然无法在同一位置匹配cat。你想要的是一个lookbehind (?<!cat),它检查前面的字符是不是cat。不幸的是,JavaScript不支持lookbehind。
  2. 您希望逻辑这两个外观。在您的情况下,如果任何一个环视失败,则会导致模式失败。因此,需要满足(在两端都有cat)的要求。但你实际上想要 OR 。如果支持看起来像(?<!cat)dog|dog(?!cat)的看起来很好看(注意交替将整个模式分开)。但正如我所说,不支持lookbehinds。您似乎在第一个catdogdog位中使用* OR *编辑两个外观的原因是前面的cat没有被检查(参见第1点)。
  3. 如何解决后视镜问题呢? Kolink的回答建议(?!cat)...dog,它将看起来放在cat开始的位置,并使用前瞻。这有两个新问题:它不能匹配字符串开头的dog(因为前面的三个字符是必需的。它不能匹配两个连续的dog因为匹配不能重叠(匹配后)第一个dog,引擎需要三个...新字符,这些字符会在再次实际匹配dog之前使用下一个dog

    有时你可以通过反转模式和字符串来解决这个问题,从而将外观转变为前瞻 - 但在你的情况下会将最后的前瞻变为后视。

    仅限正则表达式的解决方案

    我们必须更加聪明一点。由于匹配不能重叠,我们可以尝试显式匹配catdogcat,而不替换它(因此在目标字符串中跳过它们),然后只替换我们找到的所有dog。我们将这两个案例交替进行,因此它们都在字符串中的每个位置都进行了尝试(catdogcat选项优先,尽管这里并不重要)。问题是如何获得条件替换字符串。但是让我们看看到目前为止我们得到了什么:

    text.replace(/(catdog)(?=cat)|dog/g, "$1[or 000 if $1 didn't match]")
    

    因此,在第一个备选方案中,我们匹配catdog并将其捕获到组1中,并检查是否还有其他cat。在替换字符串中,我们只需将$1写回。美丽的是,如果第二种选择匹配,第一组将是未使用的,因此是一个空的字符串替换。我们仅匹配catdog并使用前瞻而不是立即匹配catdogcat的原因再次是重叠匹配。如果我们使用catdogcat,那么在输入catdogcatdogcat中,第一个匹配将消耗所有内容,直到并包括第二个cat,因此第一个匹配将无法被第一个dog识别替代品。

    现在唯一的问题是,如果我们使用第二种选择,我们如何获得替换000

    不幸的是,我们无法想象不属于输入字符串的条件替换。诀窍是在输入字符串的末尾添加000,如果我们找到dog,则在前瞻中捕获它,然后将其写回:

    text.replace(/$/, "000")                            
        .replace(/(catdog)(?=cat)|dog(?=.*(000))/g, "$1$2")
        .replace(/000$/, "")
    

    第一次替换将000添加到字符串的末尾。

    第二个替换匹配catdog(检查另一个cat后跟)并将其捕获到组1(将2留空)或匹配dog并将000捕获到组2中(将组1留空)。然后我们写回$1$2,这将是未加修饰的catdog000

    第三个替换在字符串的末尾摆脱了我们无关的000

    回调解决方案

    如果您不是准备正则表达式的粉丝,也不是第二个选项中的前瞻,那么您可以使用稍微简单的正则表达式和替换回调:

    text.replace(/(catdog)(?=cat)|dog/g, function(match, firstGroup) {
        return firstGroup ? firstGroup : "000"
    })
    

    使用replace的版本,为每个匹配调用所提供的函数,并将其返回值用作替换字符串。函数first参数是整个匹配,第二个参数是第一个捕获组(如果组没有参与匹配,则为undefined)等等......

    所以在替换回调中,如果000未定义(即firstGroup选项匹配),我们可以自由地召唤我们dog,或者只返回firstGroup存在(即匹配的catdogcat选项)。这有点简洁,可能更容易理解。但是,调用该函数的开销使其成为significantly slower(尽管这是否重要取决于您想要执行此操作的频率)。选择你最喜欢的!

答案 1 :(得分:1)

您的正则表达式简化为dog(?!cat)(因为第一个lookbehind不会消耗任何内容),因此它会替换dog之后没有cat的任何实例。

试用正则表达式(?!cat).{3}dog(?!cat)