【问题标题】:boolean prop matching布尔属性匹配
【发布时间】:2017-09-03 22:47:47
【问题描述】:

我有一定的逻辑命题,我想用 C# 中的正则表达式检查它们的有效性。

每个大写字母都是一个谓词。谓词逻辑公式由谓词和连接词构成,如 ¬、⇒、⇔、⋀ 和 ⋁。但是用户输入应该是 ASCII 字符串表示法,即:

Logical notation       ASCII               
¬A                     ~(A)                 Negation

A ⇒ B                 >(A,B)               Implication

A ⇔ B                 =(A,B)               Bi-Implication

A ⋀ B                  &(A,B)               AND

A ⋁ B                  |(A,B)               OR

此外,True 和 False 用 0 和 1 表示,如下所示:&(0,1)

假设我有以下 ASCII 输入

string input1 = "&(&(=(A,B),>(&(A,B),~(C))),>(A,~(&(A,B))))"; // valid
string input2 = "1"     // valid
string input3 = "=(~(A),>(>(B,C),~(A)))" // valid
string input4 = "(~(A))" // invalid because no connective in the beginning
string input5 = ">(A,B"  // invalid because no closing parenthesis

所以,ascii 字符串应该是

  1. 单个谓词 - A-Z 或 0-1
  2. 以连接词开头并包含两个用逗号分隔的命题的字符串,这些命题可以是单个谓词,也可以是包含两个命题的连接词...

我想出了这个:

Regex checkExpression = new Regex(
                   @"([&|>=]\(([A-Z0-1]{1}|\(.*\)),([A-Z0-1]{1}|\(.*\))\))
                                          |([~]\(([A-Z0-1]{1}|\(.*\))\))");

但是,我对构建正则表达式不是很熟悉,感谢任何帮助。

【问题讨论】:

  • 您实际上必须使用执行递归或理解平衡(嵌套)文本分隔符的正则表达式。这并不难,但有点棘手。

标签: c# regex logic


【解决方案1】:

正如 Richard 所说,您应该使用 AST 来管理验证,实际上您也可以使用它开始在 C# 上构建您自己的语言。过去我为各种项目做过很多次,并且使用了一个相当不错的工具,叫做“Irony.Net”,你可以直接在代码中设计你的语法。

Irony 是一个用于在 .NET 上实现语言的开发工具包 平台。与大多数现有的 yacc/lex 风格的解决方案不同,Irony 没有 使用从语法生成的任何扫描器或解析器代码 用专门的元语言编写的规范。在讽刺中 目标语言语法使用运算符直接在 c# 中编码 重载来表达语法结构。 Irony 的扫描器和解析器 模块使用编码为 c# 类的语法来控制解析 过程。 Irony.Net CodePlex

有了这个,我们提出了一个非常基本的语法,似乎可以处理下面的案例。但是,您的示例中有一个奇怪的情况(或需要进一步解释)

  1. 1 有效但0 有效吗?
  2. 如上所述,任何谓词A-Z(大写)呢?

语法示例

[Language("Logical Proposition", "1.0", "")]
public class LogicalPropositionGrammar : Grammar
{
    public LogicalPropositionGrammar()
    {
        //syntax terminals
        var lpar = ToTerm("(");
        var rpar = ToTerm(")");
        var comma = ToTerm(",");
        var trueTerm = ToTerm("1") | "true";
        var falseTerm = ToTerm("0") | "false";

        //nonterms
        var predicate = new NonTerminal("Predicate");
        var connective = new NonTerminal("Connective");
        var pexp = new NonTerminal("PredExpression");
        var formula = new NonTerminal("Formula");
        var literal = new NonTerminal("Literal");
        var singleTerm = new NonTerminal("SingleTerm");
        var multiTerm = new NonTerminal("MultiTerm");

        //formulat non terms
        var negation = new NonTerminal("Negation");
        var implication = new NonTerminal("Implication");
        var biImplication = new NonTerminal("Bi-Implication");
        var andTerm = new NonTerminal("And");
        var orTerm = new NonTerminal("Or");

        literal.Rule = trueTerm | falseTerm;
        singleTerm.Rule = lpar + pexp + rpar; //single term is (pexp)
        multiTerm.Rule = lpar + pexp + comma + pexp + rpar; //mult term = (pexp, pexp)

        //formula rules
        negation.Rule = ToTerm("~") + singleTerm;
        implication.Rule = ToTerm(">") + multiTerm;
        biImplication.Rule = ToTerm("=") + multiTerm;
        andTerm.Rule = ToTerm("&") + multiTerm;
        orTerm.Rule = ToTerm("|") + multiTerm;

        //predicate terms
        predicate.Rule = ToTerm("A") | "B" | "C" | "D" | "E" | "F" | "G" |
                            "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" |
                            "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" |
                            "X" | "Y" | "Z" | literal;

        //predicate rule
        pexp.Rule = predicate | negation | implication | biImplication | andTerm | orTerm;

        //a base formulat
        formula.Rule = MakeStarRule(formula, pexp);

        RegisterOperators(10, "&", "~", ">", "=", "|");
        MarkPunctuation(",", "(", ")");
        MarkTransient(pexp, singleTerm);

        Root = formula;
    }
}

【讨论】:

  • 我总是讨厌人们推荐一些东西,比如 HTML 敏捷包,但从不提供可行的解决方案。我喜欢这个答案。
【解决方案2】:

我创建了一个可以做到这一点的正则表达式。正则表达式:^([01A-Z](?![01A-Z])|(?<dyadic>[|&>=]\()|(?<comma-dyadic>\,)|(?<dBracket-comma>\))|(?<unary>~\()|(?<uBracket-unary>\)))+(?(dyadic)(?!))(?(comma)(?!))(?(unary)(?!))$

它在 PCRE 中更简洁更好,因为您可以进行递归 ^([A-Z01])|([>=&|])\((?R),(?R)\)|~\((?R)\)$,但这在 C# 的正则表达式中不可用。

我必须从 C# 中学习 balancing group,因此您可能需要研究一下。

代码工作原理的细分:

^                             # Start of line
(                             # Either
    [01A-Z](?![01A-Z])|       # A symbol or bool followed by anything else
    (?<dyadic>[|&>=]\((?!,))| # Start of dyadic
    (?<comma-dyadic>,(?!\)))| # Looks for comma followed by dyadic. Pops off the dyadic stack.
    (?<dBracket-comma>\))|    # Looks for ending bracket following comma. pops off comma stack. 
    (?<monadic>~\((?!\)))|    # Start of monadic function.
    (?<uBracket-monadic>\)))  # Looks for ending bracket for unary. Pops off the monadic stack. 
+                             # Any number of times.
(?(dyadic)(?!))               # Assert dyadic stack is empty. All have a comma.
(?(comma)(?!))                # Assert comma stack is empty. All dyadic commas followed by brackets.
(?(monadic)(?!))              # Assert monadic stack is empty. All monadic expressions have closing brackets.
$                             # End of line.

一个例子demo

更新:忘记确保每个函数中都有一个参数。在 3 个位置添加了负前瞻来解决此问题。

Update2:使正则表达式只匹配单字母文字。添加了一个否定前瞻,用于检查字母或数字是否后跟字母或数字。

【讨论】:

  • 很好。我没有检查空参数,所以简化的$(,) 会通过。轻松修复。我只是在想办法做到最干净。
  • 优秀的解决方案!赏金是给你的。从现在起我可以在 16 小时内奖励它
  • 谢谢! dyadic、monadic、comma、dbracket 和 uBracket 都只是捕获组的名称。我为它们命名而不是使用数字来尝试帮助提高清晰度和稳定性。 (?&lt;dyadic&gt; ...) 是一个命名控制组。
  • 是的,经过一番研究,我明白了。老实说,你用这个正则表达式让我很开心。我开始构建解析器等。
  • 再次更新。看来我在之前评论中的具体解决方案做得不好。出于简单的风格原因,它打破了边缘案例1
【解决方案3】:

使用正则表达式进行语言解析是可能的,但很快就会变得非常复杂。

我建议使用抽象语法树 (AST)。我喜欢ANTLR。一个很好的介绍可以在ANTLR with C# – using an Abstract Syntax Tree (AST)找到。

【讨论】:

    【解决方案4】:

    如前所述,正则表达式不是很好的工具。即使你能做到(在这种情况下我相当怀疑),你通常不仅需要验证它,还需要评估。你真的不想用正则表达式来做这件事。虽然对于正则表达式方法来说这是一场噩梦,但对于语法解析器来说这是小菜一碟。如果你需要一些轻量级的东西,对于简单的情况,你甚至可以自己编写,LL(1) 分析器有非常描述性的代码:

    public class ParseException : Exception
    {
        public ParseException(string message) : base(message) { }
    }
    
    public class Analyzer
    {
        protected int position;
        protected string input;
        protected Dictionary<char, bool> predicates;
    
        public Analyzer(string input)
        {
            this.input = input;
        }
    
        public bool? Evaluate(Dictionary<char, bool> predicates = null)
        {
            position = 0;
            this.predicates = predicates;
    
            try
            {
                bool value = T();
                if (position == input.Length)
                {
                    return value;
                }
            }
            catch (ParseException)
            {
            }
    
            return null;
        }
    
        protected char GetChar()
        {
            if (position >= input.Length)
            {
                throw new ParseException("Unexpected end of input");
            }
            return input[position++];
        }
    
        protected void MatchChar(char c)
        {
            if (GetChar() != c)
            {
                throw new ParseException("Invalid input");
            }
        }
    
        protected bool T()
        {
            char c = GetChar();
            if (c == '~')
            {
                MatchChar('(');
                bool val = T();
                MatchChar(')');
                return !val;
            }
            if (c == '>')
            {
                MatchChar('(');
                bool val1 = T();
                MatchChar(',');
                bool val2 = T();
                MatchChar(')');
                return val2 || !val1;
            }
            if (c == '=')
            {
                MatchChar('(');
                bool val1 = T();
                MatchChar(',');
                bool val2 = T();
                MatchChar(')');
                return val1 == val2;
            }
            if (c == '&')
            {
                MatchChar('(');
                bool val1 = T();
                MatchChar(',');
                bool val2 = T();
                MatchChar(')');
                return val1 && val2;
            }
            if (c == '|')
            {
                MatchChar('(');
                bool val1 = T();
                MatchChar(',');
                bool val2 = T();
                MatchChar(')');
                return val1 || val2;
            }
            if (c == '0')
            {
                return false;
            }
            if (c == '1')
            {
                return true;
            }
            if (c >= 'A' && c <= 'Z')
            {                   
                if (predicates == null) { return false; }
                if (predicates.TryGetValue(c, out bool val))
                {
                    return val;
                }
                throw new ParseException("Predicate value not found");
            }
    
            throw new ParseException("Invalid input");
        }
    }
    

    你可以这样测试有效性:

    bool ok1 = new Analyzer(input1).Evaluate().HasValue;
    

    并像这样评估:

    var values1 = new Dictionary<char, bool>() { ['A'] = true, ['B'] = false, ['C'] = true };
    bool result1 = new Analyzer(input1).Evaluate(values1).Value;
    

    【讨论】:

      【解决方案5】:

      如果我理解正确,您想验证给定命题的语法。

      这可以通过循环和收缩每个有效公式到单个谓词来轻松完成,例如1。重复此操作直到余数为单个1 将表明一个有效的命题。以 No Match 结尾表示无效的命题。

      插图:

      &(&(=(A,B),>(&(A,B),~(C))),>(A,~(&(A,B))))           Proposition
      &(&(1,>(1,1)),>(A,~(1)))                             First iteration
      &(&(1,1),>(A,1))                                     Second iteration
      &(1,1)                                               Third iteration
      1                                                    Fourth iteration
      
      =(~(A),>(>(B,C),~(A)))                               Proposition
      =(1,>(1,1))                                          First iteration
      =(1,1)                                               Second iteration
      1                                                    Third iteration
      
      (~(A))                                               Proposition
      (1)                                                  First iteration
                  No Match
      
      >(A,B                                                Proposition
                  No Match
      

      (Generated with regex101)

      您的正则表达式有效,但我稍微简化了它:

      ~\([A-Z0-1]\)|[&|>=]\([A-Z0-1],[A-Z0-1]\)
      

      Here's a live demo at ideone.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多