正则表达式匹配除给定列表外的所有单词

18

我正试图编写一个替换正则表达式,用引号包围除了AND、OR和NOT之外的所有单词。

以下是我尝试过的匹配部分表达式:

(?i)(?<word>[a-z0-9]+)(?<!and|not|or)
(?i)(?<word>[a-z0-9]+)(?!and|not|or)

但是两者都不起作用。替换表达式很简单,目前将所有单词都包含在内。

"${word}"

因此

这个和这个不是那个

变成

“这个”和“这个”不是“那个”


你能提供样例输入和期望的结果(匹配或不匹配)吗? - mohammedn
(?i)(?<word>[a-z0-9]++)(?<!and|not|or) - Brad Gilbert
6个回答

14

这有点不太规范,但却可行:

(?<!\b(?:and| or|not))\b(?!(?:and|or|not)\b)

用简单明了的语言来说,这个正则表达式匹配任何一个不以 "and"、"or" 或 "not" 开头或结尾的单词边界。它只匹配整个单词,例如在单词 "sand" 后面的位置不会匹配,因为它前面有 "and"。

在零宽度向后查找断言中,在 "or" 前面加上空格是必须的,这样它就成为了一个固定长度的向后查找。尝试一下是否已经解决了你的问题。

编辑:对于字符串 "except the words AND, OR and NOT." 进行全局替换并用单引号括起来,这将返回:

'except' 'the' 'words' AND, OR and NOT.

唯一可能失败的情况是字符串以单词“or”开头。好的,它包含了一个隐藏的假设,即空格分隔您的单词。如果您了解自己的数据,这两种情况都可以迁移。 - Tomalak
像所有正则表达式一样,它看起来很疯狂但确实可行。(?<word> [a-z0-9] +)(?<! 和 | 或|不是)\ b(?!和|或|不是)谢谢。 - John
你需要什么 "(?<word>[a-z0-9]+)"?你是想用引号包围你的单词还是想从字符串中取出它们? - Tomalak
1
针对以任何给定单词开头或结尾的单词,该程序会失败。例如:"helloand not goodbye" -> "'helloand not 'goodbye'" - Markus Jarderot
谢谢你的提示,我扩展了正则表达式来解决这个问题。 - Tomalak

5

约翰,

你的正则表达式几乎正确。唯一的问题是你把前瞻放在了正则表达式的结尾而不是开头。此外,你需要添加单词边界以强制正则表达式匹配整个单词。否则,它会匹配“and”中的“nd”,“or”中的“r”等,因为“nd”和“r”不在你的负前瞻中。

(?i)\b(?!and|not|or)(?[a-z0-9]+)\b


是的,其他人把这个问题搞得比它需要的更加复杂了。特别是,没有必要使用负向(或正向)回顾或命名捕获。 - Alan Moore
两件事:首先,我得出结论,使用正则表达式中的文字 [a-z] 而不是 \pL\p{Alphabetic} 或有时候是 [[:alpha:]] 在我们后 7 位时代几乎总是太“20世纪60年代”了。第二,我发现人们经常误解什么是 \b,因此最近我在推荐时一直添加关于它的注意事项。 (是的,我知道 当然都理解这些,Jan,但很多读者可能不理解。) - tchrist

4

不知道是否有点疯狂,但我并不喜欢去对抗正则表达式;我只使用一些简单易懂的模式,并且通常会通过MatchEvaluator这种方式来解决其余的问题。

    string[] whitelist = new string[] { "and", "not", "or" };
    string input = "foo and bar or blop";
    string result = Regex.Replace(input, @"([a-z0-9]+)",
        delegate(Match match) {
            string word = match.Groups[1].Value;
            return Array.IndexOf(whitelist, word) >= 0
                ? word : ("\"" + word + "\"");
        });

(edited for more terse layout)


很遗憾,这是 .NET 2 版本,所以暂时还没有 Lambda。 - John
1
好的,我会为C# 2.0进行编辑(你是指C# 2.0,而不是.NET 2.0;你可以在C# 3.0和.NET 2.0中使用它)。 - Marc Gravell
不,这并不是懒惰。我非常喜欢这种方法,它是保持表达式可维护性的好方法。对于这个很少听到的智慧点赞。 :) - zx81

2
匹配任何一个由字母、数字或下划线(包括在\w简写字符类中定义的其他单词字符)组成的"单词",您可以使用像单词边界这样的符号。
\b(?!(?:word1|word2|word3)\b)\w+

如果“word”是一段非空白字符,其开头和结尾都是字符串的开始/结束或空格,则使用类似于空格边界的方式。
(?<!\S)(?!(?:word1|word2|word3)(?!\S))\S+

这里,这两个表达式看起来会是这样的。
\b(?!(?:and|not|or)\b)\w+
(?<!\S)(?!(?:and|not|or)(?!\S))\S+

查看 正则表达式演示(或者一个流行的regex101演示,但请注意PCRE \w 的含义与.NET \w 的含义不同。) 模式解释
  • \b - 单词边界
  • (?<!\S) - 一个负向零宽断言,匹配不紧跟非空格字符的位置,它要求当前位置的左边是字符串的开头或者一个空白字符
  • (?!(?:word1|word2|word3)\b) - 一个负向前瞻,如果当前位置的右边是紧跟着一个单词边界的word1word2word3字符序列,则匹配失败(或者,如果使用了(?!\S)表示右侧边界是空白符或字符串结尾,则当前位置的右侧必须紧跟着一个空白符或字符串结尾)
  • \w+ - 匹配1个或多个单词字符
  • \S+ - 匹配1个或多个非空白符字符
在C#和其他编程语言中,您可以通过使用管道字符连接数组/列表项来动态构建模式,如下方的演示所示:
var exceptions = new[] { "and", "not", "or" };
var result = Regex.Replace("This and This not That", 
        $@"\b(?!(?:{string.Join("|", exceptions)})\b)\w+",
        "\"$&\"");
Console.WriteLine(result); // => "This" and "This" not "That"

如果您的“单词”可能包含特殊字符,则空格边界方法更适合,并确保使用 exceptions.Select(Regex.Escape) 转义“单词”:
var pattern = $@"(?<!\S)(?!(?:{string.Join("|", exceptions.Select(Regex.Escape))})(?!\S))\S+";

注意:如果要搜索的单词太多,建立一个正则表达式字典树可能是一个更好的选择。

2

根据Tomalaks的答案:

(?<!and|or|not)\b(?!and|or|not)

这个正则表达式有两个问题:
  1. (?<! ) 仅适用于固定长度的后顾断言。

  2. 之前的正则表达式只查看了周围单词的结尾/开头,而不是整个单词。

(?<!\band)(?<!\bor)(?<!\bnot)\b(?!(?:and|or|not)\b)

这个正则表达式解决了上述两个问题。首先通过将后顾断言分成三个单独的部分来解决第一个问题。其次,在环视中添加了单词边界(\b)以解决第二个问题。

0
(?!\bnot\b|\band\b|\bor\b|\b\"[^"]+\"\b)((?<=\s|\-|\(|^)[^\"\s\()]+(?=\s|\*|\)|$))

我使用这个正则表达式来查找所有不在双引号内的单词,或者是单词 "not"、"and" 或 "or"。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接