在C#中实现函数树

时间:2012-07-12 02:21:44

标签: c# math tree

想象一下,你有一个function tree,它有许多节点,可以是操作员节点(一元或二元运算符),或终端节点(常量) )。

我刚开始把手指放到键盘上,我遇到了一个小问题。我创建了Node界面,Operator:NodeBinaryOperator:OperatorUnaryOperator:OperatorTerminal:Node结构。

在这里,我遇到了问题。我现在是否为每个可预见的功能创建一个单独的类?我是否要创建SinOperator:UnaryOperatorCosOperator:UnaryOperator以及AddOperator:BinaryOperator等等?如果你包括正向和反向三角函数及其双曲线表兄弟,可以考虑很多。

或者,我可以将它保留在Binary和Unary运算符中,并传入一个委托,该委托根据它的子节点计算该节点的值。

var sinOperator = new UnaryOperator(
                    childNode,
                    delegate()
                    {
                      return Math.Sin(childNode.GetValue());
                    });

但是那时并没有什么能阻止我在代表中放入各种疯狂的东西,打破了它作为运营商的整个概念。

注意:是的我知道只有少数运算符(+ - * / ^√),而sin / cos实际上是函数。 ..但是出于本项目的目的,我们可以假设它是一个运算符

那么你将如何在C#中构建它?

2 个答案:

答案 0 :(得分:3)

为什么不看看Linq.Expressions的工作原理?它是C#中现有的模型(以及整个.NET语言)。

在这里,您使用Linq.Expressions实现了nice example of a Derivation program(您可以检查它如何处理运算符,正弦,余弦等...)

答案 1 :(得分:0)

让我们尝试定义SinOperator:UnaryOperator

假设您在GetValue上设置了Node虚拟,SinOperator应该只提供GetValue的实现,它通过对由此获得的操作数进行操作来计算数字的Sin。计算它期望的单个子节点。

“计算单个子节点”的任务对于每个UnaryOperator都是多余的,因此应基本上属于UnaryOperator类,而SinOperator必须自己覆盖Eval方法接受单个double参数并返回double

abstract class UnaryOperator:Operator //specializes GetValue
{
    //....Rest of the implementatiom
    //....
    public sealed override double GetValue()
    {
        return Eval(_childNode.GetValue());  
    }

    protected abstract double Operate(double arg);
}

class SinOperator:UnaryOperator
{
    public override double Eval(double arg)
    {
        return Math.Sin(arg);          
    }    
}

请注意SinOperator如何偏离Node的“以专业方式获取价值”的目标。相反,它已将其责任转变为“评估价值”。这是一项全新的责任,似乎不属于原始的继承链。

那怎么去呢?答案是组合(可以看作是UnaryOperator'具有计算一元函数的算法)。

interface UnaryFunction
{
   double Eval(double arg);
}

class UnaryOperator:Operator //specializes GetValue
{
    private UnaryFunction _evaluator; //For Sin this is SinFunction

    //....Rest of the implementatiom
    //....
    public sealed override double GetValue()
    {
        return _evaluator.Eval(_childNode.GetValue());  
    }
}

class SinFuntion:UnaryFunction
{
    public override double Eval(double arg)
    {
        return Math.Sin(arg);          
    }    
}

如何获得SinOperator?我建议使用一个工厂,以便创建一个与Sinfunction绑定的UnaryOperator可以集中并始终保持一致。稍后,如果您使用新的更快的方式来计算Sine而不是Math.Sin,则可以轻松创建SinFunctionFast类,并在工厂中设置此类而不是SinFunction。< / p>

在像Java这样的语言中你可能会这样,但是,由于C#允许委托,你可以使用委托来避免定义很多函数类。 (代表们以任何方式都是定义诸如SinFunction之类的简写)。

话虽如此,我想提醒你不要反对你当前的想法:

var sinOperator = new UnaryOperator(
                    childNode,
                    delegate()
                    {        
                      return Math.Sin(childNode.GetValue());
                    });

为了防止任何委托,你应该将Math.Sin本身传递给委托,UnaryOperator应该期待一个一元委托(即接受double类型的单个参数并返回一个double)。

var sinOperator = new UnaryOperator(childNode, Math.Sin);

//In UnaryOperator Class
public override double GetValue()
{
     return _OpDelegate(_childNode.GetValue());
}

在这种情况下,建议使用工厂来创建不同的操作员。

在这里使用委托的一个潜在缺点是,在C#中无法阻止多播委托的供应,而在您的情况下,您只需要一个Uni-cast委托。另外根据Microsoft's guidelines(特别注意IComparable的示例),您应该为此特定实现选择接口而不是委托。

单播代表在接口上的一个优点是它们只是一个档次更快。但是为了从中获得任何可以想象的性能增益,你需要一个包含大量操作符的巨大功能树。