仅仅出于学术原因,我也想介绍正则表达式解决方案。大多数情况下,因为您可能正在使用唯一能够解决此问题的正则表达式引擎。
在解决了一些关于 .NET 独特功能组合的有趣问题之后,下面是可以为您提供所需结果的代码:
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