符号化表达式设计(一)

要实现一个类似于matlab可以计算表达式的程序,
例如:
x = agauss(4, 0.3, 1)    /* agauss(u, s, d) 表示产生类似于高斯分布的随机数,u表示平均值,s表示方差sigma,d表示允许的最大偏离值。 */
y = x^2 - x;    
print eval(y)    /* eval(x) 表示对x进行求值 */
与一般的计算器不一样,求值不是实时计算,而是先用符号表示,类似于包含未知变量,然后给定未知变量的值,对符号表达式进行计算。

程序设计
表达式可以用一个类似于二叉树的结构组织起来,比如 a + b, 根节点 +, 包含左右两个节点a, b作为操作数,而agauss这类特殊的函数,则可以包含一个节点数组作为参数。

所以最基本的表达式节点,就是数值节点,

class ExprNode
{
protected:
    double m_value;
public:
    ExprNode(double v=0):m_value(v){}
    virtual double Value() { return m_value; }
    void Value(double t)  { m_value = t; }
    virtual void Evaluate() {}
};

然后就是变量节点,变量会有一个名字, 然后会有一个数值或者表达式来表示它的值,因为数值也作为表达式节点,所以与表达式作为值的情况是统一的,定义如下,

class ExprVariableNode : public ExprNode
{
private:
    string m_name;
    ExprNode* m_expr;
public:
    ExprVariableNode(string nm, ExprNode* e):ExprNode(), m_name(nm), m_expr(e){}
    virtual void Evaluate() { m_value = m_expr->Value(); }
};

这里或许存在这样一个问题,对变量节点进行求值的时候,使用的是m_expr->Value(), 而不是 m_expr->Evalaue(), 这是因为我想避免嵌套求值。对于一开始给的例子,
y = x^2 - x; 如果使用前套求值, 那么 x->Evaluate()会调用两次,导致同一个式子里面x的值不同。如果要避免多次调用,就需要有个标志来表明它已经求过值,在调用Evaluate之后将其置为true,
然后在需要更新的时候将标志置为false。如果不使用标识符,我们可以在创建表达式节点的时候,将所有节点放到一个列表里面,列表里面的节点顺序会对应到求值顺序上,那么需要求值的时候,遍历列表,
对每个节点调用Evaluate,不需要进行嵌套的调用。所以是需要一个列表来保存所有创建的节点的。

除了这两个简单的节点,表达式必然需要支持四则运算等常用的操作或者函数。为每一个操作或函数创建一个节点类显然是很浪费的。考虑到四则运算都是左右两个操作数,可以将这一类节点定义如下,

class ExprOpNode : public ExprNode
{
protected:
    ExprNode* m_pleft;
    ExprNode* m_pright;
    function<double(double)> m_op;
public:
    ExprOpNode(ExprNode* l, ExprNode* r, function<double(double)> op) : ExprNode(), m_pleft(l), m_pright(r), m_op(op){}
    virtual void Evaluate() { m_value = m_op(m_pleft->Value(), m_pright->Value());}
};

在创建"+"节点的时候,将 plus<double>()传入作为op参数就可以了,那么四则运算就可以支持了。进一步,可以在ExprOpNode的基础上进一步封装,使得创建节点的时候不需要处理op参数,例如,

class ExprPlusNode : public ExprOpNode
{
public:
    ExprPlusNode(ExprNode* l,ExprNode* r) : ExprOpNode(l, r, plus<double(double)>()){}
};

对于agauss函数,使用类似于ExprOpNode的方式,则可以创建下面的节点,

template<typename FuncOp>
class ExprFuncNode : public ExprNode
{
protected:
    FuncOp m_func;
    vector<ExprNode*> m_params;
public:
    ExprFuncNode(ExprNode** params, unsigned int size):
                ExprNode()
    {
        Param(params,size);
    }
    ExprFuncNode():ExprNode(){}

    vector<ExprNode*>& Param()  { return m_params;}
    void Param(ExprNode** params, unsigned int size)
    {
        m_params = vector<ExprNode*>(params, params + size);
    }

    void AddParam(ExprNode* p)  { m_params.push_back(p);}

    virtual void Evaluate()
    {
        unsigned int N = m_params.size();
        vector<ExprValueType> t_params(N);
        for(unsigned int i=0; i<N;i++)
        {
            t_params[i] = m_params[i]->Value();
        }
        m_value = m_func(t_params);
    }
};

然后定义AGauss的仿函数,

class ExprFuncAGauss
{
private:
    const static int size = 3;
    typedef std::normal_distribution<> Dis;
    typedef std::mt19937 Gen;
    std::random_device rd;
public:
    double operator() (vector<double> params)
    {
        assert(size==params.size());
        assert(params[2]>0);

        Gen gen(rd());
        Dis dis(params[0], params[1]);
        double result = 0;
        do
        {
            result = dis(gen);
        }while(abs(result-params[0])<=params[2]);
        return result;
    }
};

由于ExprFuncNode是使用模板定义的,所以定义ExprAGaussNode比定义ExprPlusNode简单一些,直接使用typedef定义即可,如下,

typedef ExprFuncNode<ExprFuncAGauss> ExprAGaussNode;

除了使用上面的方式,我们也可以直接定义ExprAGaussNode, 如下,

class ExprAGaussNode : public ExprNode
{
private:
    ExprNode* m_mu;
    ExprNode* m_sigma;
    ExprNode* m_dlimit;
    typedef std::normal_distribution<> Dis;
    typedef std::mt19937 Gen;
    std::random_device rd;
public:
    ExprAGaussNode(ExprNode* m, ExprNode* s, ExprNode* d) : m_mu(m), m_sigma(s), m_dlimit(d){}
    virtual void Evaluate() 
    {
        Gen gen(rd());
        Dis dis(params[0], params[1]);
        double result = 0;
        do
        {
            result = dis(gen);
        }while(abs(result-params[0])<=params[2]);
        m_value = result;
    }
};

使用模板的方式,应该是更方便一些的,可以直接扩展到其它的,带有多个参数的自定义函数上,对每个自定义函数,只需要定义函数实现的仿函数就可以了。

很可惜的是,使用vector<ExprNode*> m_params可以应对任意参数个数的函数,但只能用于自定义的函数。对于cmath里面的其它简单函数,比如sin,就不能这样用了。
并且,不能像plus函数一样,作为构造函数的参数传入,也不能像agauss函数一样,作为模板参数传入。而cmath中还有很多像sin这样的函数。
我们来比较一下plus和sin函数的实现,如下,

// header: <functional>
template< class T > struct plus
{
    T operator()(const T &lhs, const T &rhs) const 
    {
        return lhs + rhs;
    }
}
// header: <cmath>
double      sin( double arg );
template< class T > complex<T> sin( const complex<T>& z );
template< class T > valarray<T> sin( const valarray<T>& va );

在仿函数的头文件里面,实现了plus的仿函数结构。而在cmath头文件里,sin使用模板函数实现多种参数类型的支持。
所以plus可以作为以类型作为模板参数,也可以以仿函数作为参数。而sin则不可以,但是sin可以作为函数指针的参数。

要实现对sin这一类简单函数的支持,显然使用模板是最方便的方法,这就需要将sin函数转化为仿函数的结构。参考http://stackoverflow.com/questions/10213427/passing-a-functor-as-c-template-parameter ,我们就有了这样的转换方法, 

template< double (*FuncPtr)(double) > struct FuncToType
{
    double operator()(double t)
    {
        return FuncPtr(t);
    }
};

然后我们定义支持一个参数的函数节点的模板,

template<typename FuncName>
class ExprFuncOneNode : public ExprNode
{
private:
    ExprNode* m_pexpr;
    FuncName m_func;
public:
    ExprFuncOneNode(ExprNode* e):m_pexor(e){}
    virtual void Evaluate() { m_value =  m_func(m_pexpr->Value());}
};

现在再来看四则运算符节点的实现,或许改成使用模板方式实现更好。所以对函数节点的实现,就可以按参数个数来分类,分别使用模板实现。

好,表达式的数据结构设计就到此为止。如果您有更好的建议,或者上面的代码或思路有问题,请多多指教。

 

 

 

posted @ 2013-03-28 11:30  Frandy.CH  阅读(796)  评论(2编辑  收藏  举报