mathematica-基于模式编程

前言

到目前为止,我们学习的编程范例都可以归类为命令式编程,程序员的工作就是一步一步地说明如何执行问题的解决方案。基于规则的编程与此完全不同。在基于规则的范例中,程序员只需写下一组规则,指定在解决问题的过程中遇到的任何表达式应该应用什么转换。程序员不需要指定执行这些规则的顺序;底层编程系统会指出这一点。(看着有点像IOC容器)。

基于规则的编程是实现数学计算的一种非常自然的方式,因为符号数学本质上是将转换规则应用到表达式(例如,微分规则,积分表)。Mathematica的多用途模式匹配能力,我们才刚刚开始探索,往往使基于规则的编程成为Mathematica程序员的选择范例。

模式

什么是模式

模式是一个Mathematica表达式,它表示整个表达式类。模式最简单的例子是一个Blank,_,它表示任何表达式。另一个例子是_f,它表示任何以f为头的表达式。我们已经在函数定义中使用了这样的模式作为形式参数。

模式本身是表达式,几乎可以为模式的任何部分提供临时名称,这允许使用规则提取和操作表达式的各个部分。这些临时名称称为模式变量。这里有三个例子:

In[2]:= (f[a]+g[b])/f[a,b]/.x_f:>x^2
Out[2]= (f[a]^2+g[b])/f[a,b]^2
In[3]:= (f[a]+g[b])/f[a,b]/.f[x_]:>x^2
Out[3]= (a^2+g[b])/f[a,b]
In[4]:= (f[a]+g[b])/f[a,b]/.f[x_,y_]:>(x+y)^2
Out[4]= (f[a]+g[b])/(a+b)^2

解构

下面演示从一个表达式中,拿出感兴趣的部分(匹配子表达式是模式非常重要的功能,能快速定位子表达式):

In[5]:= f[a]+g[b]/.x_[_]:>x
Out[5]= f+g
In[6]:= f[a]+g[b]/.x_[_,_]:>x
Out[6]= Plus
In[7]:= f[a]+g[b]/.x_[y_]:>y[x]
Out[7]= a[f]+b[g]

实际上,解构和规则替代通常比使用MapAt更容易修改子表达式, 它几乎总是更容易一眼理解destructuring操作做什么, 而不是什么一个表达式的一部分被称为长序列的下标。惟一需要使用下标而不是结构的情况是,一个表达式包含多个具有完全相同结构的子表达式,而您只希望提取或修改其中一个子表达式。

下面把一些列的点对转换成logplot适配的形式。

基于函数编程的方法1:

data={{x1,y1},{x2,y2},{x3,y3},{x4,y4}};
In[102]:= Transpose[data]
Out[102]= {{x1,x2,x3,x4},{y1,y2,y3,y4}}
In[103]:= MapAt[Log,%,{2}]
Out[103]= {{x1,x2,x3,x4},{Log[y1],Log[y2],Log[y3],Log[y4]}}
In[104]:= Transpose[%]
Out[104]= {{x1,Log[y1]},{x2,Log[y2]},{x3,Log[y3]},{x4,Log[y4]}}

基于函数编程的方法2:

In[105]:= MapAt[Log,data,{All,2}]
Out[105]= {{x1,Log[y1]},{x2,Log[y2]},{x3,Log[y3]},{x4,Log[y4]}}

基于模式编程的方法(一次规则替换即可):

In[106]:= data/.{x_,y_}:>{x,Log[y]}
Out[106]= {{x1,Log[y1]},{x2,Log[y2]},{x3,Log[y3]},{x4,Log[y4]}}

测试模式

有两个方法可以测试模式:MatchQ和Cases。

MatchQ

测试一个表达式是否匹配给定的模式。

In[107]:= MatchQ[a+b+c,_Plus]
Out[107]= True

Cases

测试一个表达式(不仅仅是列表,mathematica分为表达式和子表达式)中匹配模式的所有子表达式,并且能够进行规则替换,返回最终的结果。

In[109]:= Cases[{a,a+b,a+a},x_+y_]
Out[109]= {a+b}
In[110]:= Cases[{a,a+b,a+a},x_+y_:>y]
Out[110]= {b}
In[111]:= Cases[5 (a+b)^6,_Integer]
Out[111]= {5}

还能够指定层级:

In[112]:= Cases[5 (a+b)^6,_Integer, Infinity]
Out[112]= {5,6}
In[113]:= Cases[5 (a+b)^6,_Symbol, Infinity]
Out[113]= {a,b}

也能显示头部(头部也是符号):

In[114]:= Cases[5 (a+b)^6,_Symbol, Infinity,Heads->True]
Out[114]= {Times,Power,Plus,a,b}

属性的作用

Mathematica非常善于解构。考虑下面的例子:

In[118]:= Length/@{a+b+c,x_+y_}
Out[118]= {3,2}
In[119]:= a+b+c/. x_+y_:>{x,y}
Out[119]= {a,b+c}

为什么会这样匹配呢?因为Mathematica知道Plus是一个结合运算子,比如a+b+c==a+(b+c)。这些信息被编入Plus函数的属性中:

In[120]:= Attributes[Plus]
Out[120]= {Flat,Listable,NumericFunction,OneIdentity,Orderless,Protected}

Listable表示函数可以自动作用于列表。
Flat表示函数是可结合的。
OneIdentity表示Plus[x]==x。
Orderless表示Plus是可交换的,如,Plus[a,b]==Plus[b,a]。
Protected表示不能给Plus定义新的规则,除非先Unprotect它。

In[122]:= Cases[{a+b*c,a*b+c},x_+y_*z_]
Out[122]= {a+b c,a b+c}

这种行为允许一些相当复杂的转换,并且只需要很少的工作。例如,下面的规则将展开任意因子的乘积,只要其中任何因子至少包含两个附加项:

expandrule=x_(y_+z_):>x y+x z;
In[124]:= a(b+c)(d+e+f)/.expandrule
Out[124]= a b (d+e+f)+a c (d+e+f)
In[125]:= %/.expandrule
Out[125]= a b d+a c d+a b (e+f)+a c (e+f)
In[126]:= %/.expandrule
Out[126]= a b d+a c d+a b e+a c e+a b f+a c f

先使用基于过程编程的迭代方式(注意三个等号):

In[4]:= Module[
			{init=a(b+c)(d+e+f),last},
			While[
				True,
				last=init/. expandrule;
				If[last===init,Break[],init=last]
			];
			last
		]
Out[4]= a b d+a c d+a b e+a c e+a b f+a c f

可以使用基于函数编程的迭代方式:

In[127]:= FixedPoint[#/.expandrule&,a(b+c)(d+e+f)]
Out[127]= a b d+a c d+a b e+a c e+a b f+a c f

也可以使用基于模式编程的迭代方式:

In[128]:= a (b+c)(d+e+f)//.expandrule
Out[128]= a b d+a c d+a b e+a c e+a b f+a c f

使用模式的函数

Count:计算表达式中匹配模式的子表达式的个数。
Position:计算表达式中匹配模式的子表达式的位置。
DeleteCases:删除表达式中匹配模式的子表达式,可以指定层级。

规则和函数

在本节中,我们将看到规则和函数之间有着密切的联系。然而,在继续之前,我们需要引入一种新类型的规则。

延迟规则

正如有两种类型的赋值操作Set和SetDelayed一样,也有两种类型的规则:Rule和RuleDelayed。这两种形式的区别在于,直到模式变量被替换之后,才会对RuleDelayed的右边进行计算。可以使用语法a:> b指定延迟规则。

expr=Sqrt[u-1]/Sqrt[u^2-1];
In[20]:= expr/.u^2-1->(u-1)(u+1)
Out[20]= Sqrt[-1+u]/Sqrt[(-1+u) (1+u)]

这个规则在这个简单的情况下是适用的,但在一个更复杂的情况下,它会成为一个负担,必须提供多项式的因式形式。在这种情况下,我们可以尝试以下形式的规则。

In[22]:= expr/. 1/Sqrt[y_]->1/Sqrt[Factor[y]]
Out[22]= Sqrt[-1+u]/Sqrt[-1+u^2]

问题是,Factor[y]立即计算为普通的y,所以规则的作用是用y替换y_完全没有变化!防止在替换之前对规则的右边进行计算是这类问题的解决方案。

In[23]:= expr/. 1/Sqrt[y_]:>1/Sqrt[Factor[y]]
Out[23]= Sqrt[-1+u]/Sqrt[(-1+u) (1+u)]

函数定义是规则

当你在Mathematica中定义一个函数时,你实际上是在定义一个规则。函数f的规则可以使用DownValues[f]来显示:

In[27]:= fact[n_Integer]:=Times@@Range[n]
DownValues[fact]
Out[28]= {HoldPattern[fact[n_Integer]]:>Times@@Range[n]}

HoldPattern类似Hold,保持参数不被计算,不同于Hold的是,HoldPattern用于模式识别的忽略。

函数创建的规则和一般的规则不同的地方是,前者是全局作用的,后者只在Replace类型操作符(/.)中有效。内核匹配一个表达式到一个规则时,为替换为规则的右边。可以把内核执行表达式看成是:

expression //. (all global rules)

换句话说,全局规则持续被应用直到表达式停止改变。也正是由于这个原因Mathematica内核主执行程序被成为无限执行系统。这也解释了为什么只执行表达式到一半如此地难。

根据Roman Maeder,一个Mathematica编程语言的设计者之一,模式匹配和规则替换是Mathematica实现所有其他编程结构的底层机制。这个事实,和把所有东西都统一表示为表达式,赋予了Mathematica强大的能力和灵活性。

同一个符号的多个定义

Mathematica允许为同一个符号书写多个函数定义,来覆盖(大概)不同的情况。下面是fact的递归定义:

Clear[fact]
fact[0]=1;
fact[n_Integer]:=n*fact[n-1]
In[6]:= DownValues[fact]
Out[6]= {HoldPattern[fact[0]]:>1,HoldPattern[fact[n_Integer]]:>n fact[n-1]}

同一个函数有多个定义类似C++语言的重载,但事实上更加强大。最明显的一点,Mathematica模式可以测试不仅仅是一个参数的“类型”。

不太明显,或许更重要的是,不同的规则不必不相交(可以有交集):注意到0是整数,但当参数是0时,规则fact[0]被使用而不是fact[_Integer]。这阐明Mathematica模式匹配引擎的一般策略:总是在更一般的规则之前尝试更具体的规则。DownValues[f]展示内核尝试作用的f的有序规则。也可以使用?sym查看一个符号所有规则的顺序。

当哪个规则更具体不明显时,Mathematica保持它们录入时的顺序。在fact例子中,内部的规则是相同的,无论哪个规则先录入,因为模式0比模式_Integer更加具体。其他的情况Mathematica可以不会总是有能力指出规则的正确顺序。这些情况,需要显示重排一个符号的DownValues。

最后,可以使用=(Set)或:=(SetDelayed)来定义fact的基本情况,因为右边是一个常数。

基于模式的函数定义的优势

把一个函数实现为一个规则的而不是一整个代码块有很多优势。

首先,基于模式函数通常比都在一个函数的定义更加快,因为大部分情况下Mathematica的模式匹配引擎执行规则集比显示条件声明(比如,If,Switch)更加快速。

其次,使用多个规则定义函数对数学家来说非常自然。比如,下面是绝对值函数的定义,在笔记本中的呈现是平行的:

absval[x_]:=x /;x>=0
absval[x_]:=-x /;x<0

再次,很多数学简单地涉及根据识别的模式重写表达式(比如,微分和积分)。手动做数学简化时,其实在脑中做模式识别。这样,基于模式编程经常产生关于数学问题的清晰,优雅的方案。本书的剩余部分将会看到关于这些的例子。

第四,从算法的角度,不需要限制我们的想法把全局规则看成函数。创建f[constant]任意定义的能力允许我们把全局规则想成一种存储知识的方式——基于规则。标准包MiscellaneousCityData和MiscellaneousChemicalElements是以这种方式考虑全局规则的极好例子。

第五,一个Mathematica函数在执行时可以增加它自己的规则,允许耗时的计算被缓存。

选择性清空定义

Clear[f]清空所有定义,从头再来。可以使用“f[patt]=.”选择性地清空一个定义。这个操作被称为Unset。

fact[n_]=.

也可以通过赋值到DownValues[f]直接修改函数地DownValues。比如:

DownValues[f]:=Drop[DownValues[f],{i}]

“纯”基于函数编程

除了把规则写成函数定义,也可以本地地使用ReplaceRepeated把规则应用到表达式,本质上模仿内核的操作。比如,这里是另外一种方法计算阶乘。注意必须按如下顺序指定规则,否则f[0]永远不会被使用。

In[21]:= f[5]//. {f[0]:>1,f[n_]:>n*f[n-1]}
Out[21]= 120

与递归相比,这个技术不构造一个大的执行栈,所以不受$RecursionLimit“安全保障”影响。

ReplaceRepeated和它地变体顺序地扫描用来模式匹配地规则列表。对于长的规则列表,Replace-类的操作可以使用Dispatch函数来为规则表生成一个dispatch table来提高速度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值