【问题标题】:How to make a recursive Regex如何制作递归正则表达式
【发布时间】:2021-05-04 06:54:48
【问题描述】:

我想创建一个能够为我匹配一些逻辑公式的正则表达式,以验证输入。

基本上,我会有一个数字(0-100)、一个关键字(AND/OR)和括号。

所以我应该能够匹配这类用例:

1 AND 2
(1 AND 2) OR 3
1 AND (2 OR 3)
(1 AND 2) OR (3 AND 4)
1 AND (2 OR (3 AND (4 OR 5)))

我不需要验证它是最简单的形式(比如阻止用户写(1 AND (2 AND 3))

到目前为止,我已经创建了这个:

\(?(\d+)\s+(AND|OR)\s+\d+\)?

但我被困在几件事上:

  1. 是否有某种方法可以使用正则表达式(在 C# 中)进行递归处理?因为我不应该限制括号级别。我们的目标是让\d+ 使用相同的整个表达式
  2. 我不确定如何强制执行,如果添加了左括号,我也应该有右括号?

【问题讨论】:

  • 恕我直言,编写解析器并检查表达式是否可以解析应该比可能或不可能可能的过于复杂的正则表达式产生更快的结果和更好的可维护代码。
  • @FranzGleichmann 你是对的,但更多的是更好地理解正则表达式并学习。
  • Regex - 是一种常规语法。您通常想要通过使用带有堆栈的硬编码自动机(如建议的那样)或使用框架根据 LR/LL 语法解析输入来完成:ANTLR、Gold、pegasus 等。例如,ANTLR 有大量语法:github.com/antlr/grammars-v4 .将正则表达式用于除常规结构(非递归)之外的任何东西实际上/科学上都是一个坏主意:解析 html、表达式等不是正则表达式范围。

标签: c# .net regex


【解决方案1】:

您为这项工作使用了错误的工具。 .NET 正则表达式不提供递归,尤其是我不确定它是否能识别所有 CFG,这本身就是一个有趣的问题1。因此,如果您的问题是关于学术兴趣的,那么您可以提前阅读。如果您想在某些生产代码中实际使用它,那么不要。

话虽如此,要匹配括号,您可以使用balancing groups。从匹配尖括号的文档中调整示例,您会得到:

^[^\(\)]*(((?'Open'\()[^\(\)]*)+((?'Close-Open'\))[^\(\)]*)+)*(?(Open)(?!))$

括号内的内容由上面的表达式[^\(\)]* 定义。我试图将数字和/或规则纳入其中,但没有失去理智并创建超出联邦法规长度的正则表达式,但没有成功。

但是,我们可以结合使用多个正则表达式来验证您的输入。首先,忽略括号并确保表达式是一般形式number (AND/OR number)*

^[\(\)]*([0-9]|[1-9][0-9]|100)(\s(AND|OR)\s[\(\)]*([0-9]|[1-9][0-9]|100)[\(\)]*)*$

上面的模式已经排除了空括号和带有单个数字的括号。仍然要排除在单个括号内使用多个 AND/OR 的字符串,例如 1 OR 2 AND 31 OR (2 AND 3 AND 4)1 AND 2 OR (1 AND 2)

(\d)+\s*(AND|OR)\s*(\d)+\s*(AND|OR)

因此,要验证您的输入,您将运行所有三个正则表达式并断言前两个匹配,而最后一个不匹配。

顺便说一句,您可以使用alternation 作为分支指令,并将这三个正则表达式组合成一个断言与前两个匹配而与最后一个不匹配的正则表达式。这留给读者作为练习,因为这将再次无法阅读。

您可以找到一个有效的 dotnetfiddle 演示 here。以下代码供参考:

using System;
using System.Text.RegularExpressions;
                    
var correctParantheses = new Regex(@"^[^\(\)]*(((?'Open'\()[^\(\)]*)+((?'Close-Open'\))[^\(\)]*)+)*(?(Open)(?!))$");
var correctStructure = new Regex(@"^[\(\)]*([0-9]|[1-9][0-9]|100)(\s(AND|OR)\s[\(\)]*([0-9]|[1-9][0-9]|100)[\(\)]*)*$");
var ambiguousClauses = new Regex(@"(\d)+\s*(AND|OR)\s*(\d)+\s*(AND|OR)");

bool Validate(string input) => 
    !string.IsNullOrWhiteSpace(input) && 
    correctParantheses.IsMatch(input) && 
    correctStructure.IsMatch(input) && 
    !ambiguousClauses.IsMatch(input);

var validInputs = new [] 
{
    "1 AND 2",
    "(1 AND 2) OR 3",
    "1 AND (2 OR 3)",
    "(1 AND 2) OR (3 AND 4)",
    "1 AND (2 OR (3 AND (4 OR 5)))",
    "((((1 AND 2) OR 3) AND 4) OR 5)",
    "1"
};

var invalidInputs = new [] 
{
    "(1 AND 2)) OR 3",
    "((1 AND 2) OR 3",
    "(1)",
    "()",
    "101",
    "1 AND AND 2",
    "AND 1 AND",
    "AND",
    "1 1 AND 2",
    "1 OR 2 AND 3",
    "1 OR (2 AND 3 AND 4)",
    "1 AND 2 OR (1 AND 2)"
};

foreach (var validInput in validInputs)
{
    var isValid = Validate(validInput);
    if (!isValid)
    {
        throw new Exception($"Valid input {validInput} did not validate.");
    }
}

foreach (var invalidInput in invalidInputs)
{
    var isValid = Validate(invalidInput);
    if (isValid)
    {
        throw new Exception($"Invalid input {invalidInput} validated.");
    }
}

Console.WriteLine("All cases passed.");

1 直觉上它可以,因为平衡组基本上允许您实现堆栈并使用回溯,应该有一种方法来实现不确定性。但我没有证据或反例。

【讨论】:

  • 太棒了:)。我需要一些时间才能完全理解它,但它很棒。我认为正则表达式本身非常雄辩地说明了为什么在生产中使用它们不是最好的主意;)
【解决方案2】:

Regex - 是一种常规语法。它解析常规且通常是非递归结构,通常并不意味着解析递归结构,例如:JSON、HTML、数学表达式、布尔表达式、C++、C#、Java 等。它只是不适合其他类型的语法,正则表达式适用于有限状态自动机,而您的任务涉及上下文(这就是需要堆栈的原因),因此您应该使用下一级语法层次结构:

https://en.wikipedia.org/wiki/Formal_grammar#The_Chomsky_hierarchy

您通常希望通过使用带堆栈的硬编码自动机(如 cmets 中建议的)或使用框架根据 LR/LL 语法解析输入来完成:ANTLR、Gold、pegasus 等。

例如,有大量的 ANTLR 语法:github.com/antlr/grammars-v4。 如果稍微研究一下语法,您会发现您的问题在多种语法中都有答案:

https://github.com/antlr/grammars-v4/blob/master/arithmetic/arithmetic.g4

【讨论】:

  • 似乎有一个(?R) 合成器,这更像是我在寻找的东西,但我无法弄清楚。我珍惜你的时间,但这并不能回答我的问题。
  • C# 有平衡组,您可以查看它们,但请注意 - 这是可憎的,您的团队会因此而讨厌您。使用硬编码版本 - 它会花费更少的精力和更多的收获。
猜你喜欢
  • 2012-01-16
  • 2011-10-30
  • 1970-01-01
  • 2012-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-21
  • 2014-12-10
相关资源
最近更新 更多