如何显示变量的公式

5

我得到了一些复杂的代码,可以显示一些数字。该代码仍在开发中,尚未完成。现在最终用户想要了解数字是如何计算出来的。

因此,以下是一个非常简单的示例:

var x = y + z; // x = 10 + 20

Display(x); // x will be 30

但我希望有一个类似于帮助字符串的东西

"x = y(10) + z(20) = 30"

这是一个非常简单的例子,通常我会得到像树结构一样深奥的公式。

是否有人做过类似的事情并有一些技巧和提示?

我尝试创建我的双精度类,并覆盖 +-*/ 运算符,并创建一个字符串。但代码变得非常丑陋。


1
你可能需要将表达式表示为二叉树,其中每个非叶子节点都是运算符(例如 +),每个叶子节点都是数字。然后可以评估您的树(+ 节点通过添加其两个子节点的值返回一个值,等等),或将其转换为字符串(+ 节点通过获取其两个子节点的字符串值并在它们之间添加 "+" 符号来构造一个值)。当您想要避免不必要的括号时(例如显示 "a + b + c" 而不是 "(a + b) + c")时,生活变得有趣。 - canton7
将采用二叉树模型。 :) - PartySvensken
有趣的问题 :-) - Samuel Vidal
5个回答

2
你应该考虑使用一个数字类,它可以自动跟踪数字的操作。我曾经在Python中做过这样的事情,由于C#也知道运算符重载,所以在那里也是可能的。
这样一个名为"TracingNumber"的类应该覆盖您计算中要使用的所有运算符。当调用时,它应该使用标准实现来计算值,并额外跟踪输入和运算符。这样,该类自动构建表示计算的树形结构。
有了这个,你可以询问每个结果是如何计算的,它将能够告诉你它的来源和用于创建它的操作。以及它的值。如果您还为原始值添加名称,这也可以用于输出包含数字名称而不仅仅是它们的值的公式。

2
如果你将表达式构建为二叉树,其中叶子节点是值,非叶子节点是对这些值进行操作的运算符,那么你可以做到这一点。构建它们很笨拙,但你有很多的能力。
using System;

public class Program
{
    public static void Main()
    {
        var result = new AssignmentNode("x", new AddNode(new ValueNode("y", 10), new ValueNode("z", 20)));
        Console.WriteLine(result.GetStringValue());

    }
}

public abstract class ArithmeticNode
{
    public abstract double GetNumericValue();
    public abstract string GetStringValue();
}

public abstract class OperatorNode : ArithmeticNode
{
    public ArithmeticNode Left { get; }
    public ArithmeticNode Right { get; }
    public string Symbol { get; }

    protected OperatorNode(ArithmeticNode left, ArithmeticNode right, string symbol)
    {
        Left = left;
        Right = right;
        Symbol = symbol;
    }

    protected abstract double Operate(double left, double right);

    public override double GetNumericValue()
    {
        return Operate(Left.GetNumericValue(), Right.GetNumericValue());    
    }

    public override string GetStringValue()
    {
        return string.Format("({0} {1} {2})", Left.GetStringValue(), Symbol, Right.GetStringValue());
    }
}

public class AddNode : OperatorNode
{
    public AddNode(ArithmeticNode left, ArithmeticNode right)
        : base(left, right, "+") { }

    protected override double Operate(double left, double right)
    {
        return left + right;    
    }
}

public class ValueNode : ArithmeticNode
{
    public string Name { get; }
    public double Value { get; }

    public ValueNode(string name, double value)
    {
        Name = name;
        Value = value;
    }

    public override double GetNumericValue()
    {
        return Value;
    }

    public override string GetStringValue()
    {
        return string.Format("{0}({1})", Name, Value);
    }
}

// Represents an expression assigned to a variable
public class AssignmentNode : ArithmeticNode
{
    public string Name { get; }
    public ArithmeticNode Body { get; }

    public AssignmentNode(string name, ArithmeticNode body)
    {
        Name = name;
        Body = body;
    }

    public override double GetNumericValue()
    {
        return Body.GetNumericValue();
    }

    public override string GetStringValue()
    {
        return string.Format("{0} = {1} = {2})", Name, Body.GetStringValue(), Body.GetNumericValue());
    }
}

输出:

x = (y(10) + z(20)) = 30)

消除括号,但仅在必要的情况下,本身就是一项挑战。

执行速度也不会是世界上最快的 - 您已将快速数值相加交换为缓慢的虚方法调用。您可以通过使用编译表达式来改进:

using System;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        var result = new AssignmentNode("x", new AddNode(new ValueNode("y", 10), new ValueNode("z", 20)));
        Console.WriteLine(result.GetStringValue());

    }
}

public abstract class ArithmeticNode
{
    private Func<double> computer;

    public abstract string GetStringValue();

    public abstract Expression GetExpression();
    public double GetNumericValue()
    {
        if (computer == null)
        {
            computer = Expression.Lambda<Func<double>>(GetExpression()).Compile();
        }
        return computer();
    }
}

public abstract class OperatorNode : ArithmeticNode
{
    public ArithmeticNode Left { get; }
    public ArithmeticNode Right { get; }
    public string Symbol { get; }

    protected OperatorNode(ArithmeticNode left, ArithmeticNode right, string symbol)
    {
        Left = left;
        Right = right;
        Symbol = symbol;
    }

    protected abstract Expression Operate(Expression left, Expression right);

    public override Expression GetExpression()
    {
        return Operate(Left.GetExpression(), Right.GetExpression());    
    }

    public override string GetStringValue()
    {
        return string.Format("({0} {1} {2})", Left.GetStringValue(), Symbol, Right.GetStringValue());
    }
}

public class AddNode : OperatorNode
{
    public AddNode(ArithmeticNode left, ArithmeticNode right)
        : base(left, right, "+") { }

    protected override Expression Operate(Expression left, Expression right)
    {
        return Expression.Add(left, right); 
    }
}

public class ValueNode : ArithmeticNode
{
    public string Name { get; }
    public double Value { get; }

    public ValueNode(string name, double value)
    {
        Name = name;
        Value = value;
    }

    public override Expression GetExpression()
    {
        return Expression.Constant(Value);
    }

    public override string GetStringValue()
    {
        return string.Format("{0}({1})", Name, Value);
    }
}

// Represents an expression assigned to a variable
public class AssignmentNode : ArithmeticNode
{
    public string Name { get; }
    public ArithmeticNode Body { get; }

    public AssignmentNode(string name, ArithmeticNode body)
    {
        Name = name;
        Body = body;
    }

    public override Expression GetExpression()
    {
        return Body.GetExpression();
    }

    public override string GetStringValue()
    {
        return string.Format("{0} = {1} = {2})", Name, Body.GetStringValue(), Body.GetNumericValue());
    }
}

可能有一种解析Linq表达式以产生相同结果的方法,但这超出了我的能力范围。


2
我最近做了类似的事情,并以基本相同的方式使用了树结构。我没有使用二叉树 - 我允许每个级别有多个子节点 - 并且我手动从算术中分离出了“帮助”(我称之为这样),但它完成了工作。这样会更灵活,但不太自动化,需要更多的工作。
从设计的角度来看,我可能想要封装一个变量类来表示值的推导思想,将其称为“Variable”。变量将从涉及其他变量的表达式中推导出来。变量将具有实际的数值,运算符将计算不仅新值,而且还有新的推导值。例如:
class Variable
    int id
    double value
    string expression
    Variable[] dependencies

    Variable()
        id = GetId()

    static int shared_id = 0

    static int GetId()
        return shared_id++

    static Variable Add(Variable lhs, Variable rhs)
        Variable result
        result.value = lhs.value + rhs.value
        result.expression = "v[" + lhs.id + "]+v[" + rhs.name + "]"
        result.dependencies[0] = lhs
        result.dependencies[1] = rhs
        return result


Variable x(1.0, "x", [])
// x.id = 0
// x.value = 1.0
// x.expression = "x"
// x.dependecies = []

Variable y(2.0, "y", [])
// y.id = 1
// y.value = 2.0
// y.expression = "y"
// y.dependecies = []

Variable z(3.0, "z", [])
// z.id = 2
// z.value = 3.0
// z.expression = "z"
// z.dependecies = []

Variable w = x + (y + z)
// (y + z).id = 3
// (y + z).value = 5.0
// (y + z).expression = "v[1]+v[2]"
// (y + z).dependencies = [y, z]
// w.id = 4
// w.value = 6.0
// w.expression = "v[0]+v[3]"
// w.dependencies = [x, (y + z)]

你会得到一个类似这样的推导过程:

最初的回答

v[4]=v[0]+v[3]=1.0+5.0=6.0
v[0]=x=1.0
v[3]=v[1]+v[2]=2.0+3.0=5.0
v[1]=y=2.0
v[2]=z=3.0

如果你想要内联地书写它,你可以递归地将根表达式的每个依赖项替换为其内联表达式,并用括号将每个术语括起来以确保正确排序。"最初的回答"

0
你可以创建一个函数:
public string getAddResult(int y, int z)
{
   return "x = y(" + y + ") + z(" + z + "=" + (y + z);
}

对于这个问题,可以使用与此相同的/或者将其作为参数的单一函数(+ - /)


典型的情况是我得到了一个像树形结构一样深奥的公式。 - Alfe

-1

非常感谢您提供的许多好答案。

看起来共同点是需要自定义类并添加文本字符串。

仍然没有得到真正简洁的版本。

var x = y + z; => 给出表达式。 我们需要一个额外的类不是问题,但我们需要编写“x”、“y”和“z”。


是的,变量的名称通常被编译器丢弃了。没有办法把它们找回来。 - canton7
了解并且我需要向客户解释 :) - PartySvensken

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接