Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to show formula for variables

I got some complex code that results in displaying some numbers. The code is still under development and not final. Now the end users want to understand how the numbers are calculated.

So for a very simple example:

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

Display(x); // x will be 30

But I would like to have a help string like

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

This is a very simple example, typical I got a deep formula like a tree structure.

Has anyone done something similar and have some tips and tricks?

I have tried to create my own double class that overrides +-*/ and creates a string. But the code becomes very ugly.

like image 253
PartySvensken Avatar asked Apr 03 '19 14:04

PartySvensken


2 Answers

I recently did something like this and used a tree structure in basically the same way. I didn't use a binary tree - I allowed multiple children at each level - and I constructed the "help", as I called it, manually separate from the arithmetic, but it gets the job done. This is a little more flexible but less automatic and more work.

From a design perspective probably I'd want to encapsulate the idea of a derivation of value in a class, call it Variable. Variables will be derived from Expressions involving other Variables. Variables will have an actual numeric value and operators will calculate not only the new value but also the new derivation. For example:

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)]

You get a derivation that looks like this:

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

If you wanted to write it out inline you could recursively substitute the inlined expression for each dependency of the root into the root's expression, enclosing each term with parentheses to ensure proper ordering.

like image 186
Patrick87 Avatar answered Sep 25 '22 19:09

Patrick87


If you construct your expression as a binary tree, where the leaves are values and the non-leaves are operators on those values, you can do this. It's clumsy to construct them, but you've got a lot of power:

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());
    }
}

Outputs:

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

(Getting rid of the brackets, but only where necessary, is a challenge in itself).

It's also not going to be the fastest thing in the world to execute - you've traded nice fast numeric addition for slow virtual method calls. You can improve that by using compiled expressions:

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());
    }
}

There may be a way of parsing Linq expressions to produce the same result, but that's beyond me.

like image 39
canton7 Avatar answered Sep 25 '22 19:09

canton7