【问题标题】:Expression evaluator for C#/Python/RubyC#/Python/Ruby 的表达式求值器
【发布时间】:2011-02-12 17:20:50
【问题描述】:

我们有以下格式的半复杂表达式:
“25 + [变量1] > [变量2]”

我们需要一个表达式求值器来解析表达式并使用 回调 来请求变量值并计算出表达式的整体结果。它必须是一个回调,因为有成千上万的变量。

我们需要常用的数学运算符,但也需要“if”等。语言越丰富越好。

我们可以使用任何我们想要的语言。有人有什么建议吗?

【问题讨论】:

  • 除非您对语言有确切的规范,否则我们所能做的就是建议有关语言实现的一般资源。即便如此,我们也会为您提供有用的资源,因为这是您的编译器构建课程,而不是我们的;)
  • 你所说的“半复杂”是什么意思?您的问题背后的真实情况是什么?还是只是家庭作业?如果是这样,请以适当的方式标记您的问题。谢谢

标签: c# python ruby expression parser-generator


【解决方案1】:

您是否考虑过使用 Mono.CSharp.Evaluator?似乎这与适当设置的 InteractiveBaseClass 结合使用可以很好地完成这个技巧,而且工作量很小。

请注意,以下使用 Mono 2.11.1 alpha。

using System;
using System.Diagnostics;
using Mono.CSharp;
using NUnit.Framework;

public class MonoExpressionEvaluator
{
    [Test]
    public void ProofOfConcept()
    {
        Evaluator evaluator = new Evaluator(new CompilerContext(new CompilerSettings(), new ConsoleReportPrinter()));
        evaluator.InteractiveBaseClass = typeof (Variables);
        Variables.Variable1Callback = () => 5.1;
        Variables.Variable2Callback = () => 30;

        var result = evaluator.Evaluate("25 + Variable1 > Variable2");

        Assert.AreEqual(25 + Variables.Variable1 > Variables.Variable2, result);
        Console.WriteLine(result);
    }

    public class Variables
    {
        internal static Func<double> Variable1Callback;

        public static Double Variable1 { get { return Variable1Callback(); } }

        internal static Func<double> Variable2Callback;

        public static Double Variable2 { get { return Variable2Callback(); } }
    }
}

真的很遗憾它运行有点慢。例如,在我的 i7-m620 上运行 10,000 次需要将近 8 秒:

[Test]
public void BenchmarkEvaluate()
{
    Evaluator evaluator = new Evaluator(new CompilerContext(new CompilerSettings(), new ConsoleReportPrinter()));
    evaluator.InteractiveBaseClass = typeof(Variables);
    Variables.Variable1Callback = () => 5.1;
    Variables.Variable2Callback = () => 30;

    var sw = Stopwatch.StartNew();
    for (int i = 1; i < 10000; i++)
        evaluator.Evaluate("25 + Variable1 > Variable2");
    sw.Stop();

    Console.WriteLine(sw.Elapsed);
}

00:00:07.6035024

如果我们可以将其解析并编译为 IL 以便我们可以以 .NET 速度执行它,那就太好了,但这听起来有点像白日梦......

[Test]
public void BenchmarkCompiledMethod()
{
    Evaluator evaluator = new Evaluator(new CompilerContext(new CompilerSettings(), new ConsoleReportPrinter()));
    evaluator.InteractiveBaseClass = typeof(Variables);
    Variables.Variable1Callback = () => 5.1;
    Variables.Variable2Callback = () => 30;

    var method = evaluator.Compile("25 + Variable1 > Variable2");
    object result = null;
    method(ref result);
    Assert.AreEqual(25 + Variables.Variable1 > Variables.Variable2, result);

    Variables.Variable2Callback = () => 31;
    method(ref result);
    Assert.AreEqual(25 + Variables.Variable1 > Variables.Variable2, result);

    var sw = Stopwatch.StartNew();
    for (int i = 1; i < 10000; i++)
        method(ref result);
    sw.Stop();
    Console.WriteLine(sw.Elapsed);
}

00:00:00.0003799

天哪。

需要像 IF 这样的类似 excel 的表达式结构吗?建立你自己的!

    [Test]
    public void ProofOfConcept2()
    {
        Evaluator evaluator = new Evaluator(new CompilerContext(new CompilerSettings(), new ConsoleReportPrinter()));
        evaluator.InteractiveBaseClass = typeof(Variables2);
        Variables.Variable1Callback = () => 5.1;
        Variables.Variable2Callback = () => 30;

        var result = evaluator.Evaluate(@"IF(25 + Variable1 > Variable2, ""TRUE"", ""FALSE"")");

        Assert.AreEqual("TRUE", result);
        Console.WriteLine(result);
    }

    public class Variables2 : Variables
    {
        public static T IF<T>(bool expr, T trueValue, T falseValue)
        {
            return expr ? trueValue : falseValue;
        }
    }

【讨论】:

    【解决方案2】:

    查看NCalc。它是 .NET,应该支持您的要求。

    【讨论】:

      【解决方案3】:

      纯表达式求值器实际上很容易编写。

      查看这个 SO 答案,它显示了十几种语言的表达式评估器。您应该能够适应其中之一:

      Code Golf: Mathematical expression evaluator (that respects PEMDAS)

      编辑:显然,无论是谁,都没有去那里检查解决方案。是的,有一堆是为了满足高尔夫规则(通常是“最小”)而挤得满满当当的,但其中大部分都用明文版的算法解释得很清楚。

      【讨论】:

      • 那些对于 if 构造或变量回调之类的东西没有太多可扩展性。
      • 我同意。人们甚至并没有真正尝试打高尔夫球那里的许多解决方案。
      • 它们并不难修改。 "if(a,b,c)" 只是你必须解析的一段语法;解析是提供解决方案的一部分。变量的回调只是意味着当检测到变量时,您需要在代码中添加回调。您当然必须对这些进行一些自定义。我的观点是它们是如此简单,以至于尝试找到一个完全符合您要求的子程序包并没有多大意义;你可以准确地编写你想要的代码。这些通常是 100-200 行!如果 OP 无法编写这些代码,那么软件包也对他没有多大帮助。
      • @Ken:你的评论来得有点尴尬,因此有点模棱两可:你同意 Gabe 还是我?
      【解决方案4】:

      嗯……你需要一门语言。您拥有 C#、VB.Net、IronPython、IronRuby 等。

      使用正则表达式简单地替换打开的变量(也许您甚至提前知道它们并且只需要一个字符串。替换)然后使用 CodeDOM(对于 C# 或 VB.Net)或使用 DLR(IronPython、IronRuby)编译脚本。您可以简单地将变量作为方法参数添加到用于封装代码(对于 CodeDOM)的方法包装器中,或者只是将变量注入 DLR。 我们在业务团队中实施的这两种变体都省力且可靠。

      当您紧急请求回调时,请在上述解决方案中添加一个方法,该方法与编程语言的主机通信,名称如 ValueOf(string)。所以你可以写

      ValueOf("A") > ValueOf("B") - 10

      玩得开心。

      【讨论】:

        【解决方案5】:

        http://code.google.com/p/bc-expression/

        通过 lambda 或块回调处理变量查找。

        了解数字、字符串和布尔常量。

        一元运算符 + - !

        运算符 || && = > + - * / %

        用 ( ) 分组

        如果存在语法错误,则引发 Expression::SyntaxError。

        【讨论】:

          猜你喜欢
          • 2023-04-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-19
          • 1970-01-01
          相关资源
          最近更新 更多