C#/Python/Ruby的表达式求值器

1 投票
5 回答
2067 浏览
提问于 2025-04-16 11:42

我们有一些比较复杂的表达式,格式是:
"25 + [Variable1] > [Variable2]"

我们需要一个表达式计算器来解析这个表达式,并使用一个回调函数来请求变量的值,然后计算出整个表达式的结果。之所以要用回调函数,是因为变量的数量可能有成千上万。

我们需要常见的数学运算符,还有像“如果”这样的功能。语言越丰富越好。

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

5 个回答

1

纯表达式求值器其实写起来挺简单的。

你可以看看这个StackOverflow的回答,里面展示了很多种语言的表达式求值器。你应该能根据其中一种进行调整:

代码高尔夫:数学表达式求值器(遵循PEMDAS规则)

编辑:谁要是给这个点了差评,显然没有去看看那里的解决方案。没错,有一些是为了符合高尔夫规则(通常是“最小”)而写得很紧凑,但大多数都有很清晰的解释和算法的明文版本。

3

你有没有考虑过使用 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

哎呀。

需要像 Excel 那样的表达式构造,比如 IF 吗?那就自己动手做吧!

    [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的工具,应该能满足你的需求。

撰写回答