前言
Mathematica基于过程的编程结构只是一个门面:内部更深层次的,是函数编程语言。
函数式编程范式中,函数和数据是没有区别的。函数可以和其他数据一样操控,包括作为参数和作为返回值。可以写个程序书写其他程序并且执行它们。这也是为何函数编程在人工智能领域如此流行的原因:它允许创造能在运行时修改它们行为的程序。
另外,函数程序没有赋值语句和循环。作为赋值的代替,数据严格通过函数调用和返回移动。作为循环的代替,高级函数作用函数到数据集合,或者使用递归。因为函数编程和过程编程如此不同,需要更多努力才能掌握,但是回报是产出优雅,精确和强大的程序的能力。
基本函数编程
Map和Apply
Map作用函数于表达式的每个子表达式并返回结果。Apply作用函数于表达式的所有子表达式一次并返回结果。
纯函数
使用Function定义纯函数:
Function[{x,y,...},body]
也可以使用简写形式(#表示参数,#1第一个参数,#2第二个参数,依次类推,&表示函数):
Sqrt[#]&
上面的表达式表示#的平方根函数。
层级指定
Level可以指定表达式的层级:
Level[list,lev]
lev的表示规则如下:
- n表示1到n
- {n}表示仅n层
MapAt
作用函数于表达式指定位置的子表达式。
同一个主题的变体
Map有很多的变体。
MapThread和Thread
MapIndexed
Through
Scan
迭代函数
给定迭代次数或者直到条件满足为止。
Nest
作用一个函数于返回值n次。
Nest[f,init,n]
FixedPoint
重复作用一个函数于返回值直到结果不变为止。
FixedPoint[f,init]
Fold
类似reduce,作用两个参数的函数于初始值和表达式的子表达式。
Fold[f,x,list]
Throw&Catch
抛出和捕获提供了一种改变Mathematica计算执行流的机制。Mathematica包含函数,例如Break, Continue,和Return,用于改变或终止循环和过程的执行。不幸的是,这些函数不能用于退出任何函数迭代结构。例如,当下面定义的函数f被传递给NestList时,f中的返回表达式对计算没有明显的影响:
In[3]:= f[x_]:=(If[x>5,Return[x+1]];2x)
NestList[f,1,6]
Out[4]= {1,2,4,8,9,10,11}
这种行为的原因是返回导致f而不是NestList返回。如果有某种方法可以同时从多个函数返回,这将非常有用。这就是Throw和Catch的目的。
In[29]:= Clear[f]
f[x_]:=(If[x>5,Throw[x+1]];2x)
Catch[NestList[f,1,3]]
Out[31]= {1,2,4,8}
没有触发Throw,返回列表。
In[32]:= Catch[NestList[f,1,6]]
Out[32]= 9
触发了Throw,返回Throw里面的表达式。
递归
分而治之是一种算法策略,通过将问题分解成一个或多个相同类型的小问题来解决问题。这个过程一直持续到所考虑的问题非常小,以至于它们的解决方案微不足道。然后将子解组合成原问题的解。
递归是函数调用自身的行为。支持递归的编程语言(一个包含几乎所有现代语言的类)使得分治算法的实现非常简单。这是因为递归机制自动处理子解决方案的组合。
基础
毫无疑问,最著名的递归函数是阶乘:
In[34]:= Clear[fact]
fact[n_Integer]:=If[n==0,1,n fact[n-1]]
Array[fact,5]
Out[36]= {1,2,6,24,120}
如您所见,事实函数调用自身。这样做没有任何问题,只要“buck”在某处停止(在本例中,当n变为0时)。
要递归地解决一个问题,你必须考虑两种情况:
- 对于非平凡的情况,如何将问题简化为一个更小的问题(或几个更小的问题)。
- 基本情况是什么必须有一种方法来停止递归。
在列表上递归
反序排列
In[44]:= Clear[rev];
rev[s_List]:=If[s=={},{},Append[rev[Rest[s]],First[s]]];
rev[{1,2,3,4,5}]
Out[46]= {5,4,3,2,1}
递归的伟大之处在于,所有这些混乱的东西都可以通过普通的函数调用机制进行跟踪。递归允许我们在一个非常高的层次上对分而治之的问题进行推理,而细节则自行处理。
最小值
作为另一个简单的例子,这里有一个函数,它查找lis中的最小值。基本上,这个函数会查看列表中的前两个元素,然后在递归之前删除这两个元素中较小的那个。当只剩下一个元素时,它必须是最小值,因此返回它。
In[59]:= Clear[minimum];
minimum[s_List]:=Which[
s=={},Infinity,
Length[s]==1,s[[1]],
s[[1]]>s[[2]],minimum[Drop[s,{1}]],
True,minimum[Drop[s,{2}]]
];
minimum[{3,5,2,6,4}]
Out[61]= 2
归并排序
递归的一个更有趣的例子是用于排序列表的归并排序算法。
归并排序的策略是:
- 将列表分成两个大小差不多相同的部分。
- 递归地对每一块排序。
- 合并已排序的块。
In[66]:= Clear[mergesort]
mergesort[s_List]:=
Switch[
Length[s],
0,{},
1,s,
_,With[{half=Quotient[Length[s],2]},
merge[mergesort[Take[s,half]],mergesort[Drop[s,half]]]
]
];
merge[a_List,b_List]:=
Which[
a=={},b,
b=={},a,
a[[1]]<b[[1]],Prepend[merge[Rest[a],b],a[[1]]],
True,Prepend[merge[Rest[b],a],b[[1]]]
];
mergesort[{30,57,2,73,17,4,100,74,84,28,32,91}]
Out[69]= {2,4,17,28,30,32,57,73,74,84,91,100}
操作表达式
非原子表达式的一般结构是head[part1,…, partn],其中head和每个部分都是其他表达式,我们可以使用FullForm函数来探索各种表达式的内部表示,例如,
Plus[a,b]
下标表达式
类似面对对象语言的get方法,获取对象的单个属性。
Part[expre,{i,j,…}]
In[71]:= (a/b)//FullForm
Out[71]//FullForm= Times[a,Power[b,-1]]
In[72]:= (a/b)[[2,1]]
Out[72]= b
使用Position查询子表达式的位置:
In[73]:= Position[(a/b),b]
Out[73]= {{2,1}}
表达式层级
类似面对对象语言获取对象的所有属性。
如下表达式的树状图表示:
{a,b,{c,d,{e,f,g}}}
第一层的所有子表达式(类似对象的所有字段):
In[94]:= Level[{a,b,{c,d,{e,f,g}}},{1}]
Out[94]= {a,b,{c,d,{e,f,g}}}
第二层的所有子表达式:
In[95]:= Level[{a,b,{c,d,{e,f,g}}},{2}]
Out[95]= {c,d,{e,f,g}}
第三层的所有子表达式:
In[96]:= Level[{a,b,{c,d,{e,f,g}}},{3}]
Out[96]= {e,f,g}
1到1层的所有子表达式:
In[102]:= Level[{a,b,{c,d,{e,f,g}}},1]
Out[102]= {a,b,{c,d,{e,f,g}}}
1到2层的所有子表达式:
In[101]:= Level[{a,b,{c,d,{e,f,g}}},2]
Out[101]= {a,b,c,d,{e,f,g},{c,d,{e,f,g}}}
1到3层的所有子表达式:
In[103]:= Level[{a,b,{c,d,{e,f,g}}},3]
Out[103]= {a,b,c,d,e,f,g,{e,f,g},{c,d,{e,f,g}}}
等价于(把每一层的子表达式拼接起来)
In[104]:= Join[Level[{a,b,{c,d,{e,f,g}}},{1}],Level[{a,b,{c,d,{e,f,g}}},{2}],Level[{a,b,{c,d,{e,f,g}}},{3}]]
Out[104]= {a,b,{c,d,{e,f,g}},c,d,{e,f,g},e,f,g}
表达式上的函数操作
Map、Operate、Apply、MapAt等。
列表上的各种操作
FullForm、Drop、Append、Take、Reverse、RotateLeft、Join等。