【问题标题】:Evaluating string "3*(4+2)" yield int 18 [duplicate]评估字符串“3 *(4 + 2)”产生int 18 [重复]
【发布时间】:2010-09-24 22:14:27
【问题描述】:

.NET 框架中是否有一个函数可以计算字符串中包含的数值表达式并返回结果? F.e.:

string mystring = "3*(2+4)";
int result = EvaluateExpression(mystring);
Console.Writeln(result); // Outputs 18

是否有标准的框架函数可以替换我的EvaluateExpression 方法?

【问题讨论】:

    标签: c# string math numeric evaluate


    【解决方案1】:

    如果要计算字符串表达式,请使用以下代码 sn-p。

    using System.Data;
    
    DataTable dt = new DataTable();
    var v = dt.Compute("3 * (2+4)","");
    

    【讨论】:

    • 在应用于老派 .Net 时,我从这个答案中得到了很多用处,但新的 Core 框架没有 DataTables。 .Net Core 中有什么可以做到这一点的吗?
    • .NET Core 现在支持 DataTable:blogs.msdn.microsoft.com/devfish/2017/05/15/…
    • 感觉用数据表做简单的数学计算很脏。 NCalc 或其他一些特定于数学的库将成为 iMO 更好的主意。
    • 有趣的是,当前的框架实现也不依赖于现有的组件。 System.Data 命名空间有它自己的(内部)实现,一个成熟的词法分析器/解析器,带有relational data-oriented capabilities
    • 这是迄今为止最好、最简单、最快的解决方案,而且开销最小。如果可以的话,我会+2
    【解决方案2】:

    使用编译器来做意味着内存泄漏,因为生成的程序集被加载并且从未释放。它的性能也低于使用真正的表达式解释器。为此,您可以使用Ncalc,这是一个仅具有此目的的开源框架。如果已经包含的变量和自定义函数还不够,您还可以定义自己的变量和自定义函数。

    例子:

    Expression e = new Expression("2 + 3 * 5");
    Debug.Assert(17 == e.Evaluate());
    

    【讨论】:

    • 使用编译器也是一个巨大的安全风险。您打算允许使用数字表达式,但突然间您暴露了更像 PowerShell 的东西
    • 此解决方案是否也对 Min、Max、Avg 等运算符有帮助?
    【解决方案3】:

    试试这个:

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

    【讨论】:

    • 有人能解释一下为什么会这样吗?
    • @ChrisTrombley:DataColumn.Expression 属性支持一种包含基本算术运算符和一些有用函数的小语言。 @Petar 所做的是创建一个新列,并将其 Expression 属性设置为指定的表达式。之后,当他访问该列的值时,DataTable 计算表达式并计算值,然后将其返回给调用者。有关支持的运算符和函数的详细信息,请参阅msdn.microsoft.com/en-us/library/…
    【解决方案4】:

    您可以查看“XpathNavigator.Evaluate”,我用它来处理我的 GridView 的数学表达式,它对我来说很好。

    这是我用于我的程序的代码:

    public static double Evaluate(string expression)
    {
        return (double)new System.Xml.XPath.XPathDocument
        (new StringReader("<r/>")).CreateNavigator().Evaluate
        (string.Format("number({0})", new
        System.Text.RegularExpressions.Regex(@"([\+\-\*])")
        .Replace(expression, " ${1} ")
        .Replace("/", " div ")
        .Replace("%", " mod ")));
    }
    

    【讨论】:

      【解决方案5】:

      这是一个使用堆栈的简单表达式评估器

      public class MathEvaluator
      {
          public static void Run()
          {
              Eval("(1+2)");
              Eval("5*4/2");
              Eval("((3+5)-6)");
          }
      
          public static void Eval(string input)
          {
              var ans = Evaluate(input);
              Console.WriteLine(input + " = " + ans);
          }
      
          public static double Evaluate(String input)
          {
              String expr = "(" + input + ")";
              Stack<String> ops = new Stack<String>();
              Stack<Double> vals = new Stack<Double>();
      
              for (int i = 0; i < expr.Length; i++)
              {
                  String s = expr.Substring(i, 1);
                  if (s.Equals("(")){}
                  else if (s.Equals("+")) ops.Push(s);
                  else if (s.Equals("-")) ops.Push(s);
                  else if (s.Equals("*")) ops.Push(s);
                  else if (s.Equals("/")) ops.Push(s);
                  else if (s.Equals("sqrt")) ops.Push(s);
                  else if (s.Equals(")"))
                  {
                      int count = ops.Count;
                      while (count > 0)
                      {
                          String op = ops.Pop();
                          double v = vals.Pop();
                          if (op.Equals("+")) v = vals.Pop() + v;
                          else if (op.Equals("-")) v = vals.Pop() - v;
                          else if (op.Equals("*")) v = vals.Pop()*v;
                          else if (op.Equals("/")) v = vals.Pop()/v;
                          else if (op.Equals("sqrt")) v = Math.Sqrt(v);
                          vals.Push(v);
      
                          count--;
                      }
                  }
                  else vals.Push(Double.Parse(s));
              }
              return vals.Pop();
          }
      }
      

      【讨论】:

      • 很好。但它不像括号内那样遵循算术优先级,它应该首先计算除法然后乘法然后加法等等。
      • 注意到它只适用于一位数字。
      • (4+3)/2 失败(答案为 2)。
      • @DanW try: "((4+3)/2)"
      • 这也不能处理两位数
      【解决方案6】:
      static double Evaluate(string expression) { 
        var loDataTable = new DataTable(); 
        var loDataColumn = new DataColumn("Eval", typeof (double), expression); 
        loDataTable.Columns.Add(loDataColumn); 
        loDataTable.Rows.Add(0); 
        return (double) (loDataTable.Rows[0]["Eval"]); 
      } 
      

      解释它的工作原理:

      首先,我们在var loDataTable = new DataTable(); 部分中创建一个表,就像在数据库引擎中一样(例如 MS SQL)。

      然后是一列,带有一些特定参数 (var loDataColumn = new DataColumn("Eval", typeof (double), expression);)。

      "Eval" 参数是列的名称(ColumnName 属性)。

      typeof (double)是要存储在列中的数据类型,等于把System.Type.GetType("System.Double");改为。

      expressionEvaluate方法接收到的字符串,存储在列的属性Expression中。此属性用于一个非常特定的目的(很明显),即放在列上的每一行都将用“表达式”填充,并且它实际上接受可以放入 SQL 查询的任何内容。请参阅http://msdn.microsoft.com/en-us/library/system.data.datacolumn.expression(v=vs.100).aspx 以了解可以在 Expression 属性中放入什么以及如何评估它。

      然后,loDataTable.Columns.Add(loDataColumn); 将列 loDataColumn 添加到 loDataTable 表中。

      然后,通过loDataTable.Rows.Add(0); 将一行添加到具有Expression 属性的个性化列的表中。当我们添加这一行时,表loDataTable的“Eval”列的单元格会自动填充其“Expression”属性,如果它有运算符和SQL查询等,它会被评估然后存储到单元格中,所以,这里发生了“魔术”,带有运算符的字符串被评估并存储到一个单元格中......

      最后,只需将存储的值返回到第 0 行中“Eval”列的单元格(它是一个索引,从零开始计数),并使用 return (double) (loDataTable.Rows[0]["Eval"]); 转换为双精度数。

      这就是全部……工作完成!

      这里有一个更容易理解的代码,它的作用相同......它不在方法内部,它也被解释了。

      DataTable MyTable = new DataTable();
      DataColumn MyColumn = new DataColumn();
      MyColumn.ColumnName = "MyColumn";
      MyColumn.Expression = "5+5/5"
      MyColumn.DataType = typeof(double);
      MyTable.Columns.Add(MyColumn);
      DataRow MyRow = MyTable.NewRow();
      MyTable.Rows.Add(MyRow);
      return (double)(MyTable.Rows[0]["MyColumn"]);
      

      首先,用DataTable MyTable = new DataTable();创建表

      然后,有DataColumn MyColumn = new DataColumn();的列

      接下来,我们为列命名。这样我们就可以在它存储到表时搜索它的内容。通过MyColumn.ColumnName = "MyColumn";完成

      然后是表达式,这里我们可以放一个字符串类型的变量,这里有一个预定义的字符串“5+5/5”,结果是6。

      要存储到列MyColumn.DataType = typeof(double);的数据类型

      将列添加到表中...MyTable.Columns.Add(MyColumn);

      在表格中插入一行,复制表格结构DataRow MyRow = MyTable.NewRow();

      使用MyTable.Rows.Add(MyRow);将行添加到表中

      并返回表格MyTablereturn (double)(MyTable.Rows[0]["MyColumn"]);MyColumn列第0行单元格的值

      课程结束!!!

      【讨论】:

        【解决方案7】:

        这是从右到左执行,所以需要使用适当的括号来执行表达式

            // 2+(100/5)+10 = 32
            //((2.5+10)/5)+2.5 = 5
            // (2.5+10)/5+2.5 = 1.6666
            public static double Evaluate(String expr)
            {
        
                Stack<String> stack = new Stack<String>();
        
                string value = "";
                for (int i = 0; i < expr.Length; i++)
                {
                    String s = expr.Substring(i, 1);
                    char chr = s.ToCharArray()[0];
        
                    if (!char.IsDigit(chr) && chr != '.' && value != "")
                    {
                        stack.Push(value);
                        value = "";
                    }
        
                    if (s.Equals("(")) {
        
                        string innerExp = "";
                        i++; //Fetch Next Character
                        int bracketCount=0;
                        for (; i < expr.Length; i++)
                        {
                            s = expr.Substring(i, 1);
        
                            if (s.Equals("("))
                                bracketCount++;
        
                            if (s.Equals(")"))
                                if (bracketCount == 0)
                                    break;
                                else
                                    bracketCount--;
        
        
                            innerExp += s;
                        }
        
                        stack.Push(Evaluate(innerExp).ToString());
        
                    }
                    else if (s.Equals("+")) stack.Push(s);
                    else if (s.Equals("-")) stack.Push(s);
                    else if (s.Equals("*")) stack.Push(s);
                    else if (s.Equals("/")) stack.Push(s);
                    else if (s.Equals("sqrt")) stack.Push(s);
                    else if (s.Equals(")"))
                    {
                    }
                    else if (char.IsDigit(chr) || chr == '.')
                    {
                        value += s;
        
                        if (value.Split('.').Length > 2)
                            throw new Exception("Invalid decimal.");
        
                        if (i == (expr.Length - 1))
                            stack.Push(value);
        
                    }
                    else
                        throw new Exception("Invalid character.");
        
                }
        
        
                double result = 0;
                while (stack.Count >= 3)
                {
        
                    double right = Convert.ToDouble(stack.Pop());
                    string op = stack.Pop();
                    double left = Convert.ToDouble(stack.Pop());
        
                    if (op == "+") result = left + right;
                    else if (op == "+") result = left + right;
                    else if (op == "-") result = left - right;
                    else if (op == "*") result = left * right;
                    else if (op == "/") result = left / right;
        
                    stack.Push(result.ToString());
                }
        
        
                return Convert.ToDouble(stack.Pop());
            }
        

        【讨论】:

        • 这个工作正常,另一个类似的不适用于 2 位数字。
        【解决方案8】:

        编辑: 意识到我真的应该把加法和减法分开,让它更符合 BODMAS。

        非常感谢 Rajesh Jinaga 的基于 Stack 的方法。我发现它对我的需求非常有用。下面的代码是对 Rajesh 方法的轻微修改,它首先处理除法,然后是乘法,最后是加法和减法。它还允许在表达式中使用布尔值,其中 true 被视为 1,false 被视为 0。允许在表达式中使用布尔逻辑。

        public static double Evaluate(string expr)
            {
                expr = expr.ToLower();
                expr = expr.Replace(" ", "");
                expr = expr.Replace("true", "1");
                expr = expr.Replace("false", "0");
        
                Stack<String> stack = new Stack<String>();
        
                string value = "";
                for (int i = 0; i < expr.Length; i++)
                {
                    String s = expr.Substring(i, 1);
                    // pick up any doublelogical operators first.
                    if (i < expr.Length - 1)
                    {
                        String op = expr.Substring(i, 2);
                        if (op == "<=" || op == ">=" || op == "==")
                        {
                            stack.Push(value);
                            value = "";
                            stack.Push(op);
                            i++;
                            continue;
                        }
                    }
        
                    char chr = s.ToCharArray()[0];
        
                    if (!char.IsDigit(chr) && chr != '.' && value != "")
                    {
                        stack.Push(value);
                        value = "";
                    }
                    if (s.Equals("("))
                    {
                        string innerExp = "";
                        i++; //Fetch Next Character
                        int bracketCount = 0;
                        for (; i < expr.Length; i++)
                        {
                            s = expr.Substring(i, 1);
        
                            if (s.Equals("(")) bracketCount++;
        
                            if (s.Equals(")"))
                            {
                                if (bracketCount == 0) break;
                                bracketCount--;
                            }
                            innerExp += s;
                        }
                        stack.Push(Evaluate(innerExp).ToString());
                    }
                    else if (s.Equals("+") ||
                             s.Equals("-") ||
                             s.Equals("*") ||
                             s.Equals("/") ||
                             s.Equals("<") ||
                             s.Equals(">"))
                    {
                        stack.Push(s);
                    }
                    else if (char.IsDigit(chr) || chr == '.')
                    {
                        value += s;
        
                        if (value.Split('.').Length > 2)
                            throw new Exception("Invalid decimal.");
        
                        if (i == (expr.Length - 1))
                            stack.Push(value);
        
                    }
                    else
                    {
                        throw new Exception("Invalid character.");
                    }
        
                }
                double result = 0;
                List<String> list = stack.ToList<String>();
                for (int i = list.Count - 2; i >= 0; i--)
                {
                    if (list[i] == "/")
                    {
                        list[i] = (Convert.ToDouble(list[i - 1]) / Convert.ToDouble(list[i + 1])).ToString();
                        list.RemoveAt(i + 1);
                        list.RemoveAt(i - 1);
                        i -= 2;
                    }
                }
        
                for (int i = list.Count - 2; i >= 0; i--)
                {
                    if (list[i] == "*")
                    {
                        list[i] = (Convert.ToDouble(list[i - 1]) * Convert.ToDouble(list[i + 1])).ToString();
                        list.RemoveAt(i + 1);
                        list.RemoveAt(i - 1);
                        i -= 2;
                    }
                }
                for (int i = list.Count - 2; i >= 0; i--)
                {
                    if (list[i] == "+")
                    {
                        list[i] = (Convert.ToDouble(list[i - 1]) + Convert.ToDouble(list[i + 1])).ToString();
                        list.RemoveAt(i + 1);
                        list.RemoveAt(i - 1);
                        i -= 2;
                    }
                }
                for (int i = list.Count - 2; i >= 0; i--)
                {
                    if (list[i] == "-")
                    {
                        list[i] = (Convert.ToDouble(list[i - 1]) - Convert.ToDouble(list[i + 1])).ToString();
                        list.RemoveAt(i + 1);
                        list.RemoveAt(i - 1);
                        i -= 2;
                    }
                }
                stack.Clear();
                for (int i = 0; i < list.Count; i++)
                {
                    stack.Push(list[i]);
                }
                while (stack.Count >= 3)
                {
                    double right = Convert.ToDouble(stack.Pop());
                    string op = stack.Pop();
                    double left = Convert.ToDouble(stack.Pop());
        
                    if (op == "<") result = (left < right) ? 1 : 0;
                    else if (op == ">") result = (left > right) ? 1 : 0;
                    else if (op == "<=") result = (left <= right) ? 1 : 0;
                    else if (op == ">=") result = (left >= right) ? 1 : 0;
                    else if (op == "==") result = (left == right) ? 1 : 0;
        
                    stack.Push(result.ToString());
                }
                return Convert.ToDouble(stack.Pop());
            }
        

        我知道可能有一种更简洁的方式来做这件事,我想我只是分享一下它的第一眼,以防有人发现它有用。

        【讨论】:

        • 注意:堆栈增长的方式除法和减法应该被索引为 list[i + 1] operator list[i - 1]。此代码具有表达式 '5 - 3' = -2 而不是 2 的答案
        • 我从 2017 年的这篇文章开始制作我的 ExpressionEvaluator。它变成了一个成熟的图书馆。代码在github上CodingSeb.ExpressionEvaluator
        【解决方案9】:

        你可以很容易地通过 CSharpCodeProvider 运行它,并用合适的绒毛包装它(基本上是一个类型和一个方法)。同样,您可以通过 VB 等 - 或 JavaScript,正如另一个答案所建议的那样。在这一点上,我不知道框架中还有什么其他内容。

        我希望 .NET 4.0 及其对动态语言的支持可能在这方面有更好的能力。

        【讨论】:

        • 这也是非常糟糕的表现。
        • 是的。我希望 DLR 至少能在性能方面有所帮助。
        【解决方案10】:

        我最近需要为一个项目执行此操作,最终我使用IronPython 执行此操作。您可以声明引擎的一个实例,然后传递任何有效的 python 表达式并获取结果。如果您只是在做简单的数学表达式,那就足够了。我的代码最终看起来类似于:

        IronPython.Hosting.PythonEngine pythonEngine = new IronPython.Hosting.PythonEngine();
        string expression = "3*(2+4)";
        double result = pythonEngine.EvaluateAs<double>(expression);
        

        您可能不想为每个表达式创建引擎。您还需要对 IronPython.dll 的引用

        【讨论】:

        【解决方案11】:

        非常感谢拉梅什。我使用他的简单代码的一个版本从数据库中提取一个字符串,并使用它在我的代码中执行布尔运算。

        x 是 1500 或 2100 之类的数字。

        函数将是一个存储的评估,例如 x > 1400 和 x

        function = relation[0].Replace("and","&&").Replace("x",x);
        
        DataTable f_dt = new DataTable();
        var f_var = f_dt.Compute(function,"");
        
        if (bool.Parse(f_var.ToString()) { do stuff  }
        

        【讨论】:

          【解决方案12】:

          没有。您将需要使用一些外部库,或编写自己的解析器。如果您有时间这样做,我建议您编写自己的解析器,因为这是一个非常有趣的项目。否则你需要使用bcParser之类的东西。

          【讨论】:

          • 我知道这很有趣,我已经做过两次了,一次用 c,一次用 c++。我只是想知道 cs/.net 是否有内置的可能性,因为它支持反射,我怀疑这是一个很好的可能性。
          • 这个答案需要删除,因为它可以简化为评论。投票和标记以引起版主的注意。
          【解决方案13】:

          简短的回答:我不这么认为。据我所知,C# .Net 已编译(转换为字节码)并且无法在运行时评估字符串。但是,JScript .Net 可以;但我仍然建议您自己编写一个解析器和基于堆栈的评估器。

          【讨论】:

            猜你喜欢
            • 2020-04-20
            • 1970-01-01
            • 2020-02-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-04
            • 2012-07-25
            • 2016-05-22
            相关资源
            最近更新 更多