【问题标题】:Evaluate logic expression contained in a passed in string based on a list of strings根据字符串列表评估传入字符串中包含的逻辑表达式
【发布时间】:2020-10-09 02:06:19
【问题描述】:

所以我不确定如何正确表达这个问题。

如果您在下面查看,假设我有一个客户订购的选项列表(PG、PA、PM、TK、TD)。现在假设我有一些表达式,我需要针对客户订购的选项进行评估,例如:PA OR PB 其中评估客户的选项列表包含选项 PA 或 PB。看起来很简单,但这些表达式可能会增长很多。下面的代码是我想要完成的一些很好的例子。

我绝不声称拥有关于字符串解析和比较逻辑操作的知识。我正在寻找一些大方向或我的选择是什么。我看到了一些关于动态 linq、规则引擎、表达式树等的东西。它只是吸收了很多东西,我正在寻找一些可以实现我想要的方向?

我对任何方法都持开放态度。感谢任何答案!

代码:

class Program
{
    static void Main(string[] args)
    {
        //Reprsents what the customer selected
        List<string> CustomerSelectedOptins = new List<string> { "PG", "PA", "PM", "TK", "TD" };

        string LogicOperation =  "PA OR (PB AND PC)"; //Evaluates true since customer list contains PA
        string LogicOperation2 = "PF OR (NOT PB AND PM)"; //Evaluates true since customer list does not contain PB but contain PM
        string LogicOperation3 = "NOT PG AND NOT(PL AND TZ)"; //Evaluates false since the customer does have PG selected


    }
}

【问题讨论】:

  • 这里有几个关于 SO 的类似问题;搜索“评估表达式 c#”,例如stackoverflow.com/questions/53844/…
  • 自写的表达式解析器不是巫术,但也不容易。我写了一个类似的解析器,它的长度不到 200 行,但是写了两天多。它只能处理二进制中缀运算符(因此 NOT 将无法解析)。 NOTANDOR 是唯一的运算符吗?
  • 如果它可以做 XOR @Codor 我正在考虑使用 ANTLR 来开发词法分析器/解析器。我相信我找到了我需要做的事情,但需要在星期一进行测试。

标签: c# .net linq lambda expression


【解决方案1】:

您可以采取几种不同的方法。一种常见的方法是将选项检查替换为truefalse,然后使用内置表达式求值器之一来求值结果表达式。注意:XOR 不适用于这些。

public static class Evaluator {
    static Regex wordRE = new Regex(@"[A-Z]+", RegexOptions.Compiled);
    static HashSet<string> Operators = new[] { "AND", "OR", "NOT" }.ToHashSet(StringComparer.OrdinalIgnoreCase);
    public static bool Evaluate(this List<string> options, string op) {
        var opListOfOptions = wordRE.Matches(op).Select(m => m.Value).Where(w => !Operators.Contains(w));

        foreach (var option in opListOfOptions) {
            var value = options.Contains(option).ToString();
            op = op.Replace(option, value);
        }

        //return DTEval(op) == 1;
        return CompEval(op);
        //return XEval(op);
    }

    static double DTEval(string expression) {
        var dt = new DataTable();
        var loDataColumn = new DataColumn("Eval", typeof(double), expression);
        dt.Columns.Add(loDataColumn);
        dt.Rows.Add(0);
        return (double)(dt.Rows[0]["Eval"]);
    }

    static DataTable cDT = new DataTable();
    static bool CompEval(string expression) {
        return (bool)cDT.Compute(expression, "");
    }

    public static bool XEval(string expression) {
        expression = new System.Text.RegularExpressions.Regex(@"not +(true|false)").Replace(expression.ToLower(), " not(${1}) ");
        expression = new System.Text.RegularExpressions.Regex(@"(true|false)").Replace(expression, " ${1}() ");

        return (bool)new System.Xml.XPath.XPathDocument(new System.IO.StringReader("<r/>")).CreateNavigator()
                .Evaluate(String.Format("boolean({0})", expression));
    }
}

Evaluate 方法 cmets 显示了您可以使用的不同选项。

或者,您可以编写自己的表达式求值器。一个简单的是解析简单语法的递归下降评估器。这个使用 C# 优先规则,从左到右具有 OR/XOR/AND 绑定。

public class Evaluator {
    // recursive descent boolean expression evaluator
    // grammer:
    //    primary  = id
    //    primary = ( expr )
    //    unop = primary
    //    unop = not unop
    //    andop = unop [ and unop ]*
    //    xorop = andop [ xor andop ]*
    //    orop = xorop [ or xorop ]*
    //    expr = orop

    public class TokenList {
        List<string> tokens;
        int curTokenNum;

        static Regex tokenRE = new Regex(@"\w+|[()]", RegexOptions.Compiled);
        public TokenList(string expr) {
            curTokenNum = 0;
            tokens = tokenRE.Matches(expr).Select(m => m.Value).ToList();
        }

        public string CurToken => curTokenNum < tokens.Count ? tokens[curTokenNum] : String.Empty;
        public void MoveNext() => ++curTokenNum;
        public bool MoreTokens => curTokenNum < tokens.Count;

        public void NextToken() {
            MoveNext();
            if (!MoreTokens)
                throw new InvalidExpressionException("Expected token");
        }
    }

    static List<string> OperatorStrings = new[] { "AND", "OR", "XOR", "NOT" }.ToList();
    enum Operators { and, or, xor, not };
    static List<string> ParenStrings = new[] { "(", ")" }.ToList();
    enum Parens { open, close };

    TokenList tokens;
    List<string> trueOptions;

    public Evaluator(List<string> trueOptions) {
        this.trueOptions = trueOptions;
    }

    string curToken => tokens.CurToken;
    bool curTokenValue => trueOptions.Contains(curToken);

    bool isOperator => OperatorStrings.FindIndex(s => s.Equals(curToken, StringComparison.OrdinalIgnoreCase)) != -1;
    Operators curOp => (Operators)OperatorStrings.FindIndex(s => s.Equals(curToken, StringComparison.OrdinalIgnoreCase));

    bool isParen => ParenStrings.Contains(curToken);
    Parens curParen => (Parens)(ParenStrings.IndexOf(curToken));

    public bool id() {
        if (isOperator)
            throw new InvalidExpressionException("missing operand");
        else {
            var ans = curTokenValue;
            tokens.MoveNext();
            return ans;
        }
    }

    bool primary() {
        if (isParen)
            if (curParen == Parens.open) {
                tokens.NextToken();
                var ans = expr();
                if (!isParen || curParen != Parens.close)
                    throw new InvalidExpressionException($"missing ) at {curToken}");
                else
                    tokens.MoveNext();
                return ans;
            }
            else
                throw new InvalidExpressionException("Invalid )");
        else
            return id();
    }

    bool unop() {
        if (isOperator && curOp == Operators.not) {
            tokens.NextToken();
            return !unop();
        }
        else
            return primary();
    }

    bool andop() {
        var ans = unop();
        while (tokens.MoreTokens && isOperator && curOp == Operators.and) {
            tokens.NextToken();
            ans = ans & unop();
        }
        return ans;
    }

    bool xorop() {
        var ans = andop();
        while (tokens.MoreTokens && isOperator && curOp == Operators.xor) {
            tokens.NextToken();
            ans = ans ^ andop();
        }
        return ans;
    }

    bool orop() {
        var ans = xorop();
        while (tokens.MoreTokens && isOperator && curOp == Operators.or) {
            tokens.NextToken();
            ans = ans | xorop();
        }
        return ans;
    }

    bool expr() => orop();

    public bool Value(string exp) {
        tokens = new TokenList(exp);

        var ans = expr();
        if (tokens.MoreTokens)
            throw new InvalidExpressionException($"Unrecognized token {curToken} after expression");
        return ans;
    }
}

您可以通过创建 Evaluator 并将其传递给计算表达式来调用它:

var eval = new Evaluator(CustomerSelectedOptions);
var ans =  eval.Value(LogicOperation);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-24
    • 1970-01-01
    • 1970-01-01
    • 2012-05-29
    • 1970-01-01
    • 2015-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多