2

我一直在尝试编写一个代码来检查给定的字符串是否包含具有特定模式的特定字符串。准确地说,例如:

string mainString = @"~(Homo Sapiens means (human being)) or man or ~woman"
List<string> checkList = new List<string>{"homo sapiens","human","man","woman"};

现在,我想提取

"homo sapiens", "human" and "woman" but NOT "man"

从上面的列表中,因为它们遵循模式,即字符串后跟〜或括号内以〜开头的字符串之一。到目前为止,我想出了:

string mainString = @"~(Homo Sapiens means (human being)) or man or ~woman"
List<string> checkList = new List<string>{"homo sapiens","human","man","woman"};
var prunedList = new List<string>();
foreach(var term in checkList)
{
   var pattern = @"~(\s)*(\(\s*)?(\(?\w\s*\)?)*" + term + @"(\s*\))?";
   Match m = Regex.Match(mainString, pattern);
   if(m.success)
   {
      prunedList.Add(term);
   }
 }

但是这种模式并不适用于所有情况......有人可以建议我如何做到这一点吗?

4

5 回答 5

2

我写了一个简单的解析器,它适用于你给出的例子。

我不知道以这种模式结尾的字符串的预期行为是什么:( ~(some words即,没有有效开头的右括号)

我相信你可以清理一些...

private bool Contains(string source, string given)
{
    return ExtractValidPhrases(source).Any(p => RegexMatch(p, given));
}

private bool RegexMatch(string phrase, string given)
{
    return Regex.IsMatch(phrase, string.Format(@"\b{0}\b", given), RegexOptions.IgnoreCase);
}

private IEnumerable<string> ExtractValidPhrases(string source)
{
    bool valid = false;
    var parentheses = new Stack<char>();
    var phrase = new StringBuilder();

    for(int i = 0; i < source.Length; i++)
    {
        if (valid) phrase.Append(source[i]);

        switch (source[i])
        {
            case '~':
                valid = true;
                break;

            case ' ':
                if (valid && parentheses.Count == 0)
                {
                    yield return phrase.ToString();
                    phrase.Clear();
                }
                if (parentheses.Count == 0) valid = false;
                break;

            case '(':
                if (valid)
                {
                    parentheses.Push('(');
                }
                break;

            case ')':
                if (valid)
                {
                    parentheses.Pop();
                }
                break;
        }
    }

    //if (valid && !parentheses.Any()) yield return phrase.ToString();
    if (valid) yield return phrase.ToString();
}

以下是我使用的测试:

// NUnit tests
[Test]
[TestCase("Homo Sapiens", true)]
[TestCase("human", true)]
[TestCase("woman", true)]
[TestCase("man", false)]
public void X(string given, bool shouldBeFound)
{
    const string mainString = @"~(Homo Sapiens means (human being)) or man or ~woman";

    Assert.AreEqual(shouldBeFound, Contains(mainString, given));
}

[Test]
public void Y()
{
    const string mainString = @"~(Homo Sapiens means (human being)) or man or ~woman";
    var checkList = new List<string> {"homo sapiens", "human", "man", "woman"};
    var expected = new List<string> { "homo sapiens", "human", "woman" };

    var filtered = checkList.Where(s => Contains(mainString, s));

    CollectionAssert.AreEquivalent(expected, filtered);
}
于 2012-11-14T21:38:04.313 回答
2

平衡括号的语言不规则,因此您无法使用 RegEx 完成您想要的。更好的方法是使用带有几个计数器的传统字符串解析 - 一个用于打开括号,一个用于关闭括号 - 或堆栈来创建类似于下推自动机的模型。

要更好地了解这个概念,请查看维基百科上的 PDA。http://en.wikipedia.org/wiki/Pushdown_automaton

下面是一个使用堆栈在最外面的括号内获取字符串的示例(伪代码)。

 Stack stack = new Stack();
 char[] stringToParse = originalString.toCharArray();

 for (int i = 0; i < stringToParse.Length; i++)
 {
      if (stringToParse[i] == '(')
            stack.push(i);
      if (stringToParse[i] == ')')
         string StringBetweenParens = originalString.GetSubstring(stack.pop(), i);
 }

当然,这是一个人为的例子,需要一些工作来进行更严格的解析,但它为您提供了如何进行解析的基本概念。我遗漏了类似的东西;正确的函数名称(现在不想查找它们),如何在嵌套括号中获取文本,例如从字符串“(外部(内部))”中获取“内部”(该函数将返回“外部(内部) )"),以及如何存储返回的字符串。

于 2012-11-14T22:33:57.770 回答
2

仅仅出于学术原因,我也想介绍正则表达式解决方案。大多数情况下,因为您可能正在使用唯一能够解决此问题的正则表达式引擎。

在解决了一些关于 .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
于 2012-11-17T19:30:12.877 回答
1

使用正则表达式是不可能的。您应该放弃使用它们的想法并使用正常的字符串操作,例如IndexOf.

于 2012-11-14T21:21:40.787 回答
1

括号检查是一种上下文无关的语言或语法,需要堆栈进行检查。正则表达式适用于正则语言。它们没有内存,因此不能用于此类目的。

要检查这一点,您需要扫描字符串并计算括号:

  • 初始化count为 0
  • 扫描字符串
    • 如果当前字符是(增量count
    • 如果当前字符是)减量count
    • 如果count为负,则引发括号不一致的错误;例如,)(
  • 最后,如果count是肯定的,那么有一些未闭合的括号
  • 如果count为零,则测试通过

或者在 C# 中:

public static bool CheckParentheses(string input)
{
    int count = 0;
    foreach (var ch in input)
    {
        if (ch == '(') count++;
        if (ch == ')') count--;

        // if a parenthesis is closed without being opened return false
        if(count < 0)
            return false;
    }

    // in the end the test is passed only if count is zero
    return count == 0;
}

您会看到,由于正则表达式无法计数,因此它们无法检查此类模式。

于 2012-11-14T22:15:49.123 回答