出于学术原因,我也想介绍一下正则表达式解决方案。主要是因为您可能正在使用唯一能够解决此问题的正则表达式引擎。
在清理了一些关于 .NET 独特功能组合的 interesting issues 之后,下面是为您提供所需结果的代码:
string mainString = @"~(Homo Sapiens means (human being)) or man or ~woman";
List<string> checkList = new List<string> { "homo sapiens", "human", "man", "woman" };
// build subpattern "(?:homo sapiens|human|man|woman)"
string searchAlternation = "(?:" + String.Join("|", checkList.ToArray()) + ")";
MatchCollection matches = Regex.Matches(
mainString,
@"(?<=~|(?(Depth)(?!))~[(](?>[^()]+|(?<-Depth>)?[(]|(?<Depth>[)]))*)"+searchAlternation,
RegexOptions.IgnoreCase
);
现在这是如何工作的?首先,.NET 支持平衡组,允许检测正确嵌套的模式。每次我们使用命名捕获组捕获某些东西时
(如(?<Depth>somepattern))它不会覆盖最后一次捕获,而是被推入堆栈。我们可以使用(?<-Depth>) 从该堆栈中弹出一个捕获。如果堆栈为空(就像在当前位置不匹配的东西),这将失败。并且我们可以通过(?(Depth)patternIfNotEmpty|patternIfEmpty)检查栈是否为空。
除此之外,.NET 还拥有唯一支持可变长度后视的正则表达式引擎。如果我们可以同时使用这两个功能,我们可以查看所需字符串之一的左侧,看看当前嵌套结构之外的某处是否有 ~(。
但这里有一个问题(见上面的链接)。 Lookbehinds 在 .NET 中从右到左执行,这意味着我们需要推动关闭括号并在遇到打开括号时弹出,而不是相反。
所以这里是对那个凶残的正则表达式的一些解释(如果你从下到上阅读后面的内容会更容易理解,就像 .NET 会做的那样):
(?<= # lookbehind
~ # if there is a literal ~ to the left of our string, we're good
| # OR
(?(Depth)(?!)) # if there is something left on the stack, we started outside
# of the parentheses that end end "~("
~[(] # match a literal ~(
(?> # subpattern to analyze parentheses. the > makes the group
# atomic, i.e. suppresses backtracking. Note: we can only do
# this, because the three alternatives are mutually exclusive
[^()]+ # consume any non-parens characters without caring about them
| # OR
(?<-Depth>)? # pop the top of stack IF possible. the last ? is necessary for
# like "human" where we start with a ( before there was a )
# which could be popped.
[(] # match a literal (
| # OR
(?<Depth>[)]) # match a literal ) and push it onto the stack
)* # repeat for as long as possible
) # end of lookbehind
(?:homo sapiens|human|man|woman)
# match one of the words in the check list