负面观察正则表达式 - 只出现一次 - Java

时间:2013-06-28 23:12:24

标签: java regex string-matching

我想找一个字符串是否包含只有一个出现的单词,

e.g。

String : `jjdhfoobarfoo` , Regex : `foo` --> false

String : `wewwfobarfoo` , Regex : `foo` --> true

String : `jjfffoobarfo` , Regex : `foo` --> true

多个foo可能发生在字符串中的任何位置,因此它们可以是非连续的,

我在java中使用字符串foobarfoo测试以下正则表达式匹配,但它不起作用并返回 true

static boolean testRegEx(String str){
    return str.matches(".*(foo)(?!.*foo).*");
}

我知道这个主题可能看似重复,但我很惊讶因为当我使用这个正则表达式时:(foo)(?!.*foo).*它有效!

知道为什么会这样吗?

5 个答案:

答案 0 :(得分:2)

使用两个锚定前瞻:

static boolean testRegEx(String str){
    return str.matches("^(?=.*foo)(?!.*foo.*foo.*$).*");
}

有几个关键点是有一个负面的预测来检查2 foo的锚定开始,并且重要的是包含输入的结束。

答案 1 :(得分:1)

您可以使用此模式:

^(?>[^f]++|f(?!oo))*foo(?>[^f]++|f(?!oo))*$

它有点长但性能很好。

ashdflasd 字符串的经典示例相同:

^(?>[^a]++|a(?!shdflasd))*ashdflasd(?>[^a]++|a(?!shdflasd))*$

细节:

(?>               # open an atomic group
    [^f]++        # all characters but f, one or more times (possessive)
  |               # OR
    f(?!oo)       # f not followed by oo
)*                # close the group, zero or more times

possessive quantifier ++就像一个贪婪的量词+,但不允许回溯。

atomic group (?>..)就像一个非捕获组(?:..),但也不允许回溯。

这些功能在这里用于表演(记忆和速度),但子图案可以替换为:

(?:[^f]+|f(?!oo))*

答案 2 :(得分:1)

如果你想检查一个字符串是否只包含另一个字符串一次,这里有两个可能的解决方案,(一个是正则表达式,一个没有)

static boolean containsRegexOnlyOnce(String string, String regex) {
    Matcher matcher = Pattern.compile(regex).matcher(string);
    return matcher.find() && !matcher.find();
}

static boolean containsOnlyOnce(String string, String substring) {
    int index = string.indexOf(substring);
    if (index != -1) {
        return string.indexOf(substring, index + substring.length()) == -1;
    }
    return false;
}

所有这些都很好。以下是您的示例演示:

    String str1 = "jjdhfoobarfoo";
    String str2 = "wewwfobarfoo";
    String str3 = "jjfffoobarfo";
    String foo = "foo";
    System.out.println(containsOnlyOnce(str1, foo)); // false
    System.out.println(containsOnlyOnce(str2, foo)); // true
    System.out.println(containsOnlyOnce(str3, foo)); // true
    System.out.println(containsRegexOnlyOnce(str1, foo)); // false
    System.out.println(containsRegexOnlyOnce(str2, foo)); // true
    System.out.println(containsRegexOnlyOnce(str3, foo)); // true

答案 3 :(得分:1)

正则表达式的问题在于,第一个.*最初消耗整个字符串,然后退出,直到找到其他正则表达式可以匹配的位置。这意味着,如果字符串中有多个foo,则正则表达式将始终与最后一个匹配。从那个位置来看,前瞻也将永远成功。

用于验证的正则表达式必须比用于匹配的正则表达式更精确。你的正则表达式失败了,因为.*可以匹配sentinel字符串'foo'。您需要在您尝试匹配的匹配项之前和之后主动阻止foo的匹配。 Casimir's answer显示了一种方法;这是另一个:

"^(?>(?!foo).)*+foo(?>(?!foo).)*+$"

效率不高,但我觉得阅读起来容易得多。事实上,你可以使用这个正则表达式:

"^(?!.*foo.*foo).+$"

效率低得多,但完整的正则表达式n00b可能会弄明白它的作用。

最后,请注意,这些正则表达式中没有一个 - 我的或Casimir的 - 使用了lookbehinds。我知道这似乎是工作的完美工具,但不是。事实上,lookbehind永远不应该是你达到的第一个工具。而不仅仅是Java。无论你使用什么样的正则表达式,在正常情况下匹配整个字符串几乎总是比使用lookbehinds更容易。而且通常也更有效率。

答案 4 :(得分:0)

有人回答了这个问题,但删除了它,

以下短代码正常运行:

static boolean testRegEx(String str){
    return !str.matches("(.*?foo.*){0}|(.*?foo.*){2,}");
}

有关如何在正则表达式内部反转结果的任何想法?