控制平衡括号

时间:2014-10-22 07:41:13

标签: c# .net regex

假设我们有一个包含不同括号的字符串(在此上下文中用括号表示([{),该字符串也可能包含其他内容如{[balanced(parenthesis)]},但其余内容将被忽略。是否可以使用正则表达式来控制所有不同的括号:

  1. “关闭”,即每个“开场”括号都与“结束”括号匹配,并且
  2. 在字符串中的正确位置?

4 个答案:

答案 0 :(得分:4)

使用下面的正则表达式可以做到这一点,虽然有点难看。

这是匹配的正则表达式而不是验证正则表达式,但您可以添加锚点,通过在开头和结尾添加锚点将其转换为验证。

(?:
    (?>[^(){}\[\]]+)
    |
    (?<open>
      (?=(?<mr>\())(?<mc>)(?<ms>)\(|
      (?=(?<mc>\{))(?<mr>)(?<ms>)\{|
      (?=(?<ms>\[))(?<mc>)(?<mr>)\[
    )
    |
    (?:
        (?<-open>
          (?!\k<mc>)\}
          |
          (?!\k<mr>)\)
          |
          (?!\k<ms>)\]
        )
        (?<-mr>)(?<-mc>)(?<-ms>)
    )
)+(?(open)(?!))

由于我们无法读取顶层堆栈,因此我们必须通过3个捕获组mrmcms来模拟它。 mrmcmsopen中的项目数始终相同。当堆栈open不为空时,3个捕获组中只有一个包含相应的左括号,另外2个捕获空字符串。非空字符串捕获组始终是堆栈顶部的括号类型。

这允许我们通过断言相应的捕获组不能匹配来匹配相应的右括号,例如, (?!\k<mc>)\}。我们知道该组不能为空(因为它必须先通过(?<-open>)检查)。只剩下2个案例:

  • 如果堆栈顶部是空字符串,则反向引用将始终匹配,并且断言始终失败。
  • 如果堆栈顶部包含相应的左括号,则反向引用将失败,并且断言成功,我们继续匹配结束括号。

答案 1 :(得分:3)

事实证明这个问题已由他Kobi中的this blog post解决了。为了简单地平衡不同类型的括号,解决方案非常简单和优雅,而不像this question中的答案那样疯狂,它具有更复杂的语法。

让我引用下面博客文章的基本部分:

  

[...]谁说我必须把我匹配的东西推到堆栈?如果想将任意字符串推入堆栈怎么办?当我看到一个开放的支架时,我真的想要一个结束括号 - 但我怎么能?

     

诀窍是使用前瞻:找到我要推送的角色的下一个匹配项,然后推送它:

{(?=.*?(<Stack>}))
     

接下来,当我想匹配一个右大括号时,我已经在堆栈中拥有正确的一个。   使用这种方法,这里是一个匹配令牌的正则表达式,匹配3个不同类型的平衡括号:

(
    [^(){}\[\]]+
    | \( (?=[^)]*  (?<Stack> \) ) )
    | \[ (?=[^\]]* (?<Stack> \] ) )
    | \{ (?=[^}]*  (?<Stack> \} ) )
    | \k<Stack> (?<-Stack>)
)+?
(?(Stack) (?!))
     

当然,这种方法确实有局限性 - 你可能找不到你想推的角色(这可能是一件好事 - 它可以让你早点失败)。如果你想平衡比常量字符串更复杂的东西,它也会变得更加棘手,但这是另一个主题。

答案 2 :(得分:0)

不,您刚才描述的语言不是常规语言,因此正则表达式不适合解决该问题。您需要使用比正则表达式更强大的lexing /解析工具。

答案 3 :(得分:-2)

如果您要做的只是匹配括号,请尝试使用以下代码:

public class Program
{

    public static void Main()
    {
        string testString1 = "{[balanced(parenthesis)]}";
        string testString2 = "(test)[wrong bracket type)";
        string testString3 = "(test)[Mismatched]((sdff)";

        bool isValid1 = ValidateString(testString1);
        bool isValid2 = ValidateString(testString2);
        bool isValid3 = ValidateString(testString3);

        if (isValid1)
            Console.WriteLine("TestString1 is balanced correctly!");
        else Console.WriteLine("TestString1 is NOT balanced properly!");

        if (isValid2)
            Console.WriteLine("TestString2 is balanced correctly!");
        else Console.WriteLine("TestString2 is NOT balanced properly!");

        if (isValid3)
            Console.WriteLine("TestString3 is balanced correctly!");
        else Console.WriteLine("TestString3 is NOT balanced properly!");

    }

    public static bool ValidateString(string testString)
    {
        int p1 = 0;
        int p2 = 0;
        int p3 = 0;

        var lastOpener = new Stack<char>();

        foreach (char c in testString)
        {
            if (c == '(') {
                p1++;
                lastOpener.Push(c);
            }
            if (c == '[') {
                p2++;
                lastOpener.Push(c);
            }
            if (c == '{') {
                p3++;
                lastOpener.Push(c);
            }

            try {               
                if (c == ')' && lastOpener.Pop() == '(')
                    p1--;
                if (c == ']' && lastOpener.Pop() == '[')
                    p2--;
                if (c == '}' && lastOpener.Pop() == '{')
                    p3--;
            } catch { return false; }
        }

        if (p1 != 0 || p2 != 0 || p3 != 0)
            return false;
        return true;
    }
}

您需要做的就是调用ValidateString()方法,并将要传递的字符串传递给您。它将测试不匹配的括号(3种不同的[](){})并进行测试以确保括号在正确的位置关闭(从我的3中可以看到)测试字符串)。如果有效,它将返回true,否则返回false

它的工作原理是函数首先创建一个Stack对象。它遍历字符串中的每个字符。如果它找到一个开括号,它会将该支架推到堆栈,并将该支架的计数器增加1。如果它找到一个右括号,它会从堆栈弹出开始括号,如果它们匹配,它会减少我们的括号计数器。

在遍历每个字符后,它会测试每个不同类型括号的计数器,如果所有三个都是0,那么我们就知道它的平衡/匹配正确!

Fiddle