目录
本篇博客主要是在上一篇《每个人都该懂点函数式编程》的基础上,进一步说明“函数”在函数式编程中的重要作用。强调了函数和普通类型一样,可以赋值、存储、传参以及作为另外函数的返回值。
本文附带了一个Demo,该Demo可以将任意字符串函数表达式解析之后生成对应的函数(一元、二元以及三元),如果你输入的是一元或者二元函数表达式,则可以绘制出相应的函数图像。一元函数图像为平面曲线,二元函数图像为立体曲面。看下图:
函数表达式中只识别X、Y、Z三个自变量。
字符串解析是重点。
怎样去识别一串字符串函数表达式呢?如x^2+sin(x)*cos(y)。之后怎样去计算函数值呢?其实原理很简单,由于每个函数表达式中包含的有效符号是有限的,如X、Y、Z、+、-、*、/以及一些函数诸如log、sin、cos等等,只要我们将这些有效符号均识别筛选出来之后,再根据这些符号的优先级别生成一个函数语法树即可。
如上图所示,使用一个“树结构”去存储最终的语法树。最后带入X、Y(二元)求得函数值。
表达式解析这块难点是语法树的构建和最终求值。语法树的构建有点复杂,大家可以参见源码;最终求值的原理是,判断当前符号(节点)是单目运算符号(如cos、sin、负号等)还是双目运算符号(如+ - * /等),如果是单目运算比如cos函数,则先计算子节点(只有一个子节点)的值,然后将得到的值进行cos运算(Math.Cos(子节点的值));相反,如果是双目运算符比如+符号,那么先计算左子节点和右子节点的值,最后将两个值进行+操作(左子节点的值+右子节点的值),依次递归计算得到最终的函数值。
图像绘制这块就比较简单了。根据前一步得到的语法树,我们可以创建出对应的一元函数、二元函数以及三元函数(委托的形式)。事先定义的委托结构如下:
/// <summary>
/// 一元函数
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
public delegate double UnaryFunction(double x);
/// <summary>
/// 二元函数
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public delegate double BinaryFunction(double x,double y);
/// <summary>
/// 三元函数
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <returns></returns>
public delegate double MultiFunction(double x,double y,double z);
很简单就可以看出,一元函数接收一个参数,返回一个值;二元函数接收两个参数,返回一个值;三元函数接收三个参数,返回一个值。生成委托的过程如下:
(UnaryFunction)((double x) => { return root.GetValue(x, 0, 0); });
(BinaryFunction)((double x, double y) => { return root.GetValue(x, y, 0); });
(MultiFunction)((double x, double y, double z) => { return root.GetValue(x, y, z); });
最终给出对应的x、y、z调用委托,即可得到函数值。
一元函数绘制
随便对X取一个区间(如[-10,10]),以0.1为间距,计算每个X对应的Y值(函数值)。最终将这些点连接起来,出来的就是对应的一元函数图像。
二元函数绘制
相似的,随便对X、Y取一个区间(如X取[-10,10],Y取[-10,10]),X、Y均以0.1为间距,计算每个(X、Y)对应的Z值(函数值),最后将这些三维点绘制成曲面。
经过前几步得到了函数(委托对象),我们直接将委托对象作为属性赋给图像绘制控件,绘图控件更新界面。
//一元
if (textBox1.Text.ToLower().Contains('x') && !textBox1.Text.ToLower().Contains('y') && !textBox1.Text.ToLower().Contains('z'))
{
UnaryFunction func = (new SyntaxManager().ParseUnaryFunction(textBox1.Text));
unaryFunctionDrawingBoard1.Function = func;
tabControl1.SelectedIndex = 0;
}
//二元
else if (textBox1.Text.ToLower().Contains('x') && textBox1.Text.ToLower().Contains('y') && !textBox1.Text.ToLower().Contains('z'))
{
BinaryFunction func = (new SyntaxManager().ParseBinaryFunction(textBox1.Text));
binaryFunctionDrawingBoard1.BinaryFunction = func;
tabControl1.SelectedIndex = 1;
}
//三元
else
{
MultiFunction func = (new SyntaxManager().ParseMultiFunction(textBox1.Text));
MessageBox.Show("三元函数图像无法绘制!");
}
如上代码,函数作为属性赋值的示例。
1.Demo中有关3D图形绘制参考了网上的一个OpenTK的demo:http://download.csdn.net/detail/dragonflies/3418135#comment
2.函数图像在线生成(为了验证demo生成的图像是否正确)
3.注意 函数绘制时没有做任何区间验证,比如logX中的X不能为负,否则无效(绘制时异常)。这些需要自己注意。