Как показать формулу для переменных - PullRequest
5 голосов
/ 03 апреля 2019

Я получил сложный код, который приводит к отображению некоторых чисел.Код все еще находится в стадии разработки и не окончательный.Теперь конечные пользователи хотят понять, как рассчитываются числа.

Итак, для очень простого примера:

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

Display(x); // x will be 30

Но я хотел бы иметь строку справки, например

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

Это очень простой пример, типично я получил глубокую формулу, похожую на древовидную структуру.

Кто-нибудь делал что-то подобное и есть какие-то советы и подсказки?

Я пыталсясоздать свой собственный двойной класс, который переопределяет +-*/ и создает строку.Но код становится очень уродливым.

Ответы [ 5 ]

2 голосов
/ 03 апреля 2019

Вам следует подумать об использовании класса для ваших номеров, который автоматически отслеживает, что с ним делается. Однажды я сделал это в Python, и, поскольку C # также знает о перегрузке операторов, это также должно быть возможно там.

Такой класс TracingNumber должен переопределять все операторы, которые вы собираетесь использовать в своих вычислениях. При вызове он должен использовать стандартную реализацию для вычисления значения и дополнительно отслеживать входы и оператора. Таким образом, класс автоматически строит древовидную структуру, представляющую вычисление.

С этим вы можете спросить каждый результат, как он был вычислен, и он сможет рассказать вам об его источниках и операции, использованной для его создания. И значение, которое это привело к. Если вы также добавите имена для исходных значений, это также может быть использовано для выделения формул, которые содержат имена ваших чисел, а не только их значения.

2 голосов
/ 03 апреля 2019

Если вы создаете свое выражение в виде двоичного дерева, где листья - это значения, а не листья - это операторы этих значений, вы можете сделать это. Построить их неуклюже, но у вас много сил:

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 голосов
/ 03 апреля 2019

Недавно я сделал что-то подобное и использовал древовидную структуру в основном таким же образом.Я не использовал бинарное дерево - я позволял нескольким дочерним элементам на каждом уровне - и я построил «помощь», как я ее назвал, вручную, отдельно от арифметики, но она выполняет свою работу.Это немного более гибкий, но менее автоматический и более трудоемкий процесс.

С точки зрения дизайна, вероятно, я бы хотел инкапсулировать идею получения значения в классе, назовем его 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 голосов
/ 04 апреля 2019

Большое спасибо за много хороших ответов.

Кажется, что общая часть заключается в том, что нужен специальный класс и нам нужно добавить текстовую строку.

Все еще не получается действительно тонкая версия, которая делает.

var x = y + z; => давая выражение. То, что нам нужен дополнительный класс, не проблема, но нам нужно написать «x», «y» и «z».

0 голосов
/ 03 апреля 2019

Вы можете создать функцию:

public string getAddResult(int y, int z)
{
   return "x = y(" + y + ") + z(" + z + "=" + (y + z);
}

то же самое с этим для - / или все в одной функции с (+ - /) в качестве параметра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...