2

可能重复:
C# - Parse Math Expression
C#,用户定义的公式

该等式将仅使用加法、减法、乘法、除法运算符,不会使用括号。我不认为它会这么难,但我一直在思考它几个小时,同时尝试不同的事情并写出不同的想法。

我认为可能有某种方法可以通过拆分每个字符上的字符串并对输出做一些事情,或者通过逐个字符循环字符串并想出一些东西,但我猜我不够聪明。

无论如何,我很想听听其他人的想法,因为我很难过。我不想使用某种第三方库,这是每个人在我一直在查看的旧线程中所建议的。

4

3 回答 3

7

对于这样简单的方程,它可以用一个拆分和两个循环来实现。

对于这样的字符串:"4+5*6/2-8"

拆分运算符,将它们保留在结果中:

"4", "+", "5", "*", "6", "/", "2", "-", "8"

遍历运算符并计算乘法和除法,将结果放回列表中:

"4", "+", "30", "/", "2", "-", "8"
"4", "+", "15", "-", "8"

再次循环运算符并计算这次的加法和减法:

"19", "-", "8"
"11"
于 2012-11-04T23:08:19.907 回答
2

最简单的方法是利用 JIT 编译器来评估计算。这就是它的用途。您甚至可以将 Math.Acos(4) 之类的代码传递给表达式,或者在您正在使用的对象中“创建”一个函数 Acos,让用户不必担心数学。字首。

string code = string.Format  // Note: Use "{{" to denote a single "{" 
( 
   "public static class Func{{ public static Acos(double d) { return Math.ACos(d); }
                               public static int func(){{ return {0};}}}}", expression 
);

如果您需要任何其他功能,您也可以包含其他名称空间,但如果没有任何额外功能,代码如下所示:

using System; 
using System.Reflection; 
using System.CodeDom.Compiler; 

using Microsoft.CSharp; 

class Program 
{ 
   static void Main() 
   { 
      TestExpression("2+1-(3*2)+8/2"); 
      TestExpression("1*2*3*4*5*6"); 
      TestExpression("Invalid expression"); 
   } 

   static void TestExpression(string expression) 
   { 
      try 
      { 
         int result = EvaluateExpression(expression); 
         Console.WriteLine("'" + expression + "' = " + result); 
      } 
      catch (Exception) 
      { 
         Console.WriteLine("Expression is invalid: '" + expression + "'"); 
      } 
    } 

    public static int EvaluateExpression(string expression) 
    { 
      string code = string.Format  // Note: Use "{{" to denote a single "{" 
      ( 
         "public static class Func{{ public static int func(){{ return {0};}}}}", expression 
      ); 

      CompilerResults compilerResults = CompileScript(code); 

      if (compilerResults.Errors.HasErrors) 
      { 
         throw new InvalidOperationException("Expression has a syntax error."); 
      } 

      Assembly assembly = compilerResults.CompiledAssembly; 
      MethodInfo method = assembly.GetType("Func").GetMethod("func"); 

      return (int)method.Invoke(null, null); 
   } 

   public static CompilerResults CompileScript(string source) 
   { 
      CompilerParameters parms = new CompilerParameters(); 

      parms.GenerateExecutable = false; 
      parms.GenerateInMemory = true; 
      parms.IncludeDebugInformation = false; 

      CodeDomProvider compiler = CSharpCodeProvider.CreateProvider("CSharp"); 

      return compiler.CompileAssemblyFromSource(parms, source); 
   } 
} 

答案是从http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/abff98e3-93fe-44fa-bfd4-fcfe297dbc43/复制的,因为我不喜欢自己编写代码,感谢 Matthew华生,我不必。

于 2012-11-04T23:21:16.603 回答
0

如评论中所述,我更喜欢Recursive Descent Parsing 。这是链接的 Wikipedia 文章中的 C 示例的 C# 中非常快速的部分改编。

我发现一个简单的递归下降比调车场方法更容易阅读(注意递归下降函数如何与 EBNF 非终端定义紧密匹配)并且更具可扩展性。以下可以简单地调整以允许括号或“外部”函数。

更健壮的实现实际上将支持符号类并更优雅地处理无效语法;再一次,添加这样的递归下降解析设置是微不足道的。标记输入(阅读:拆分字符串并将数字转换为双精度)留给读者作为练习。

class RecDec {
    St x; // ugly shared state, it's a quick example
    public double eval (params object[] tokens) {
        x = new St(tokens);
        return expression();
    }
    double expression() {
        double res = term();
        string accepted;
        while ((accepted = x.acceptOp(new [] {"+", "-"})) != null) {
            res = accepted == "+"
                ? res + term()
                : res - term();
        }
        return res;
    }
    double term() {
        double res = factor();
        string accepted;
        while ((accepted = x.acceptOp(new [] {"*", "/"})) != null) {
            res = accepted == "*"
                ? res * factor();
                : res / factor();
        }
        return res;
    }
    double factor() {
        var val = x.acceptVal();
        if (val == null) {
            throw new Exception(x.ToString());
        }
        return (double)val;
    }
}

“状态”/token-feader 类:

class St {
    IEnumerable<object> src;
    public St (IEnumerable<object> src) {
        this.src = src;
    }
    public object acceptVal () {
        var first = src.FirstOrDefault();
        if (first is double) {
            src = src.Skip(1);
            return first;
        } else {
            return null;
        }
    }
    public string acceptOp (params string[] syms) {
        var first = src.FirstOrDefault();
        if (syms.Contains(first)) {
            src = src.Skip(1);
            return (string)first;
        } else {
            return null;
        }
    }
    public override string ToString () {
        return "[" + string.Join(",", src.ToArray()) + "]";
    }
}

和用法(DumpLINQPad扩展方法,使用eval返回值适用):

void Main()
{
    var rd = new RecDec();
    // Use results - i.e. Remove Dump - if not on LINQPad
    rd.eval(1d, "+", 2d).Dump();
    rd.eval(2d, "*", 1d, "+", 2d, "*", 9d, "/", 4d).Dump();
}
于 2012-11-05T00:13:39.803 回答