【问题标题】:Matching a character before or after a word, but not both in regex在一个单词之前或之后匹配一个字符,但不是在正则表达式中都匹配
【发布时间】:2012-06-23 20:12:15
【问题描述】:

假设我需要匹配一个单词word,其中可能在单词之前或之后有句点,但不能同时存在。那么word.wordword.应该匹配,但.word.不应该匹配。我将如何匹配它并捕捉单词前后发生的事情?

这是一个简化的示例,我需要将其扩展到更复杂的情况。例如,现在符号.' 可能出现在单词之前或之后,但它们只能出现一次。例如,.word'wordword.'.word' 只是有效匹配项中的一小部分,但像 .'word.' 这样的东西不应该匹配,甚至是 .'word'

上面的例子是我的主要优先事项,但额外的好处是句号和撇号的添加顺序。因此'.word.'word 应该都匹配。我认为可以解决此问题的一种方法是\.?'?|'?\.?word,但我希望通过某种方式使 OR 子句中的语句数量不依赖于符号的数量。

【问题讨论】:

  • 当你说之前或之后的文字句点时,你是指文字句点还是正则表达式句点([几乎]任何字符的通配符)?
  • 这些匹配是否会出现在较大的字符串(例如something here .word other stuff)的中间,或者可以安全地假设.word 是整个字符串?
  • word..'word'. 是否匹配错误,而 word.'word' 匹配正确?
  • @Wiseguy 会有多个相同格式的单词。所以一个可能的字符串是.word word. .word。因此所有的词都是这种格式。这些词将是不同的词,但我可以自己弄清楚。现在你可以假设这个字符串是这样的。 @LarryBattle 词。是一个很好的匹配。 。'单词'。是一场糟糕的比赛。词是一个很好的匹配。 .'word' 不匹配。另一种说法是两个符号(句号和撇号)总共可以出现 0 或 1 次,但那 1 次可以在单词之前或之后。
  • 单词“word”开始看起来很奇怪。

标签: regex


【解决方案1】:

好的。它需要更多时间才能正确处理word 出现在字符串开头或结尾的情况。

 "(?:\.word(?:[^.]|$))|(?:(?:[^.]|^)word(?:[^.]|$))|(?:(?:[^.]|^)word\.)"

LookaheadsLookbehinds 相同的 regexp(在 python 中测试):

 "(?:\.word(?:(?!\.)|$))|(?:(?:(?<!\.)|^)word(?:(?!\.)|$))|(?:(?:(?<!\.)|^)word\.)"

有效:

 re.findall(pattern(above), '.word. .word .word. word.'") // return ['.word ', ' word.'] 

【讨论】:

  • 如果word 在字符串的开头或结尾怎么办?这假设两边都有字符。此外,如果按字面意思使用,则需要转义 \.另外,我认为提问者希望将此应用于一般情况,而不仅仅是具体. 个字符。
  • @Wiseguy 是的,我正在寻找一般案例。你对这个答案的问题是正确的。
  • 我正在努力避免通过用转义句来回答这个问题来无耻地窃取声誉。特别是因为我建议进行编辑。 特别是,因为我建议它已经被编辑过,而且 .s 仍然没有转义。
  • 这可以通过使用带有反向引用的负前瞻来概括吗?
  • 老实说,我不是前瞻和后瞻方面的专家 :)
【解决方案2】:

什么口味?如果是 JavaScript,这应该可以工作:

(?:^|[^\w.'])(?=[.']*(word))(?!'*\.'*\1'*\.)(?!\.*'\.*\1\.*')([.']*)\1([.']*)

解释:

  • (?:^|[^\w.']) - 确保 word 不是较大单词的结尾,并防止正则表达式绕过前导分隔符(.')(如果它们在那里)。

  • (?=[.']*(word\b)) - 确保word 不是较长单词的开头,并且它前面除了您选择的分隔符之外什么都没有。这个时候这个词没有被消费,它只是在第 1 组中被捕获,所以它可以用来锚定接下来的两个前瞻。

  • (?!'*\.'*\1'*\.) - 仍然定位在前导分隔符(如果有)之前,这可以确保,如果单词之前有一个.,那么它之后就没有一个。

    李>
  • (?!\.*'\.*\1\.*') - 这对' 也是如此。

  • ([.']*)\1([.']*) - 最后,继续使用该单词以及任何前导或尾随分隔符,捕获第 2 组和第 3 组中的那些。

如果您使用的风格支持后视,它可能无济于事。大多数风味都对可以在后视中匹配的内容施加了严格的限制,使其对这项任务毫无用处。上面的 JavaScript 正则表达式可能仍然是您的最佳选择。然而,这个正则表达式在 .NET 和 JGSoft 中工作,这是我所知道的唯一支持完全不受限制的后视的风格:

(?<=(?:\.(?<dot1>)|'(?<apos1>))*)\bword\b(?=(?:\.(?<dot2>)|'(?<apos2>))*)(?!\k<dot1>\k<dot2>|\k<apos1>\k<apos2>)

解释:

  • (?&lt;=(?:\.(?&lt;dot1&gt;)|'(?&lt;apos1&gt;))*) - 向后扫描分隔符。当每一个都匹配时,它后面的空捕获组有效地将该字符标记为已被看到。

  • \bword\b - 消费这个词。

  • (?=(?:\.(?&lt;dot2&gt;)|'(?&lt;apos2&gt;))*) - 向前扫描以查找更多分隔符并检查它们,就像向后查找一样。

  • (?!\k&lt;dot1&gt;\k&lt;dot2&gt;|\k&lt;apos1&gt;\k&lt;apos2&gt;) - 断言单词前后都没有出现点和撇号。对空组的反向引用从不消耗任何字符,它只是断言该组已参与比赛。

在这两种风格之后,Java 的lookbehind 可能是最灵活的,但它也出了名的漏洞百出。我应该能够通过将第一个 * 更改为 {0,2} 来将此正则表达式移植到 Java,但它只会抛出“没有明显的最大长度”异常。同样,您可能最好使用上面与 JavaScript 兼容的正则表达式。

【讨论】:

  • 这与我正在寻找的非常接近。首先,我在gskinner.com/RegExr 中进行测试,但我使用的是Java,所以如果它在Java 中工作,那才是最重要的。其次,这个问题是 ..word 之类的东西不应该匹配,但它们是。想象一下 。和 ' 是只能应用于单词一次的修饰符。它们可以在之前或之后应用,但在这两个地方都没有意义。在单词之前放置两个相同的修饰符也没有意义。最后,像 %word 或 6word 这样的词匹配,但它们确实匹配。如果你能更新你的答案,那就太好了。
  • 是的,我错过了两个相同的分隔符可能出现在同一侧。这使问题变得更加……有趣。 ;) 事实上,我认为你应该放弃尝试用一个正则表达式来完成这一切。只需尝试升级这些正则表达式中的任何一个来处理三个分隔符而不是两个分隔符,你最终会得到一个无法维护的怪物。这通常是一个好兆头,正则表达式可能不是这项工作的最佳工具。
【解决方案3】:

这适用于您提供的好值和坏值的 javascript。

var func = function (str) {
    var result = true, match, re = /^([^a-z]+)[a-z]+([^a-z]+)$/i;
    if (re.test(str)) {
        match = re.exec(str);
        re = new RegExp("[" + match[1] + "]");
        result = !re.test(match[2]);
    }
    return result;
};

这里有一个简单的解释。 如果字符串在字母之前和之后包含非字母,则非字母将被提取并相互测试。否定测试结果以判断单词是好是坏。

str = .'word.
".'", "word", "."
/[.']/.test( "." )

func 函数需要一个单词(不带空格的字符)作为字符串。 如果要检查一个句子,然后用空格分隔,然后检查每个单词。 像这样。

    var sentence = "What does .'words'. means?";
var words = sentence.split(/\s+/g);
    var areWordsOk;
for( var i = 0, len = words.length; i < len; i++ ){
    areWordsOk = func( words[i] );
    if( !areWordsOk ){
        throw new Error( "bad word." ); // error is thrown
    }
}

这是我的测试用例。 现场演示:http://jsfiddle.net/Tb68G/2 这是测试用例的来源。

var func = function (str) {
    var result = true, match, re = /^([^a-z]+)[a-z]+([^a-z]+)$/i;
    if (re.test(str)) {
        match = re.exec(str);
        re = new RegExp("[" + match[1] + "]");
        result = !re.test(match[2]);
    }
    return result;
};
test("test good values", function () {
    var arr = [
        "word",
        ".word",
        "word.",
        ".word",
        "'word",
        "word.'",
        ".word'"
    ];
    var i = arr.length,
    str;
    while (i--) {
        str = arr[i];
        equal(func(str), true, str + " should be true.");
    }
});
test("test bad values", function () {
    var arr = [
        ".word.",
        ".'word.'",
        ".'word'.",
        ".'word'"
    ];
    var i = arr.length,
    str;
    while (i--) {
        str = arr[i];
        equal(func(str), false, str + " should be false.");
    }
});

【讨论】:

  • 不完全。 ^([^a-z])+ 使用单词前面的所有非字母字符,但它只捕获最后一个字符。然后(\1)$ 在字符串末尾查找相同的字符。结果:它正确地将.'word' 识别为坏的,但不是'word'.。更重要的是,您假设正则表达式正在应用于独立字符串,但我认为这个想法是在更长的字符串中找到匹配项。
【解决方案4】:

我在想正则表达式是个很酷的东西...
但有时,您需要使用其他方法,
为简单的事情看这么可怕的表达......

我说编码!

    int findWord(string text, string word, char ch, int startIdx = 0)
    {
        while(startIdx < text.Length)
        {
            int indexOf = text.IndexOf(word, startIdx);
            if (indexOf < 0) return -1;

            char preChar = (char) 0;
            char postChar = (char) 0;

            if (indexOf > 0)
                preChar = text[indexOf - 1];

            if (indexOf < text.Length - word.Length)
                postChar = text[indexOf + word.Length];


            if ((preChar == ch) ^ (postChar == ch))
            {
                return indexOf;
            }
            startIdx = indexOf + word.Length + 1;
        } 
    }

没那么简单,不止一行:)
但效果更好,一两个月后再看也能看懂。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-15
    • 2014-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多