mathematica编程最重要的特点就是函数式编程。其特点是函数可以作为数据和函数,函数可以像其他数据一样被操控,包括可以作为参数和返回值。理解了函数式编程,可以给编程带来极大的便捷。可以根据下面的用法来感受一下。
对于一个函数f[x],函数名本身也是一个表达式,可以像对待其他表达式一样对待。例如:
使用转换规则替换函数名称:
f[x] + f[1 - x] /. f -> g
out:g[1 - x] + g[x]
函数名可以通过赋值进行改变:
f = g;
f[x]
out:g[x]
函数名作为函数的参数,并调用:
g[f_, x_] := f[x] + f[1 - x]
g[Log, a]
out:Log[1 - a] + Log[a]
mathematica中有许多函数操作式函数,不仅仅可以对普通数据进行操作,还可以对函数进行操作,这也是最开始提到的函数可以作为数据进行操作。
InverseFunction求反函数:
InverseFunction[ArcSin]
out:Sin
其返回参数也是一个函数,可以进行调用:
%@Pi/2
out:0
同时,这些函数还可以对纯符号形式使用:
InverseFunction[f]
out:f^-1
(*对输出进行调用:*)
InverseFunction[f][x]
out:f^-1[x]
函数定义
函数定义包括两种,一种是对某个函数名进行定义,另一种是纯函数,也成为匿名函数,它不包含具体的函数名。
第一种方法常常用于程序中常常需要调用此函数的时候,而纯函数常用于只需要使用一次这个函数。
指定函数名的函数定义
使用模式和延迟赋值可以定义函数,例如:
f[x_] := x^2
这样定义后,之后就可以使用函数名 f 进行各种操作。
f里面的内容为一个模式,因此,可以利用模式里的内容给自变量加条件,例如:
f[x_Integer?Positive] := x^2
Mathematica中的赋值有两种方式,分别为立即赋值和延迟赋值,延迟赋值为":="。可以通过一段代码来感受一下区别。
立即赋值:
a = 1; b = 2;
c = a + b;
c
a = 2; b = 3;
c
延迟赋值:
a = 1; b = 2;
c := a + b;
c
a = 2; b = 3;
c
如果为立即赋值,两个输出均为3,也就是使用c=a+b,c赋值为3,后续当a和b更改时,c的值并不会发生变化。而对于延迟赋值的情况,输出为3和5,也就是当每次调用c的时候,都会进行a+b的运算。可以发现,这个延迟赋值的模式和函数很像,因此,函数定义的时候使用延迟赋值。延迟赋值的应用不仅仅是函数,还可以利用它的特点写出很多高效的程序。对于规则来说,也有延迟的说法,也就是":->",思想和延迟赋值是一样的。
纯函数
纯函数的定义不指定函数名,使用函数Function进行定义,也可以使用其简写形式——&。
Function用法:
Function[x,body] 单变量
Function[{x1,x2,…},body] 多变量
例如:
Function[x, x^2]
简写形式:
类似于λ表达式,自变量定义如下:
- # 纯函数中的第一个变量
- #n 纯函数中的第n个变量
- ## 纯函数的所有变量列表
- ##n 纯函数中从n个变量开始的变量列
例如:
#^2 &
注意:在纯函数中使用 & 符号时,要注意 & 的优先级很低,必要时要用括号。
多行函数的定义
上面仅仅提到了单行函数,而更多复杂的函数需要使用Module。
Module[{x,y,…},expr]
Module[{x=x0,…},expr]
其中x,y等为局部变量。例如:
f[x0_] := Module[{x = x0},
While[x > 0, x = Log[x]];
x]
f[10] // N
out:-0.181483
对列表和其他表达式应用函数
在之前的笔记里提到过,在mathematica中列表和表达式是几乎一样的,无非就是头的不同。将函数应用于列表和表达式是一样的。例如:
#^2 & /@ {a, b, c}
out:{a^2, b^2, c^2}
#^2 & /@ (a + b + c)
out:a^2 + b^2 + c^2
Apply(@@)——换头
Apply[f,{a,b,…}] 将 f 应用于列表,给出 f[a,b,…]
Apply[f,expr] or f@@expr 将 f 应用于表达式的顶层
Apply[f,expr,{1}] or f@@@expr 将 f 应用于表达式的第一层
Apply[f,expr,lev] 将 f 应用于表达式的指定层
如果不指定层数,其实就是将列表或表达式的头换成指定的函数f。在前面的表达式章节里也提过。
Map(/@)
Map[f,{a,b,…}] 将 f 应用于列表的每个元素,给出 {f[a],f[b],…}
Map[f,expr] 或 f/@expr 将 f 应用于 expr 的第一层部分
MapAll[f,expr] 或 f//@expr 将 f 应用于 expr 的所有部分
Map[f,expr,lev] 在由 lev 指定的层上将 f 应用于 expr 的各个部分
如果需要指定将f应用于指定的项上,需要使用MapAt。例如:
MapAt[f, {{a, b, c}, {d, e, f}}, {{1, 2}, {2, 1}}]
out:{{a, f[b], c}, {f[d], e, f}}
为了避免混淆,即使在仅有一个下标时,也应该以列表的形式指定每一项:
MapAt[f, {a, b, c, d}, {{2}, {3}}]
out:{a, f[b], f[c], d}
Map 把一元函数作用于一个表达式的项上;MapThread 将多元函数作用于多个表达式.
MapThread[f,{expr1,expr2,…}] 将 f 作用于每个表达式 expri 中对应的项
MapThread[f,{expr1,expr2,…},lev] 将 f 作用于 expri 的指定层
MapThread[f, {{a, b}, {ap, bp}, {app, bpp}}]
out:{f[a, ap, app], f[b, bp, bpp]}
Map等函数可以通过项的修改产生表达式,但有时不需要产生新的表达式,仅需要查看某些表达式,或者仅对表达式中的某些项进行运算。这是需要用到Scan,其语法与Map相同。
重复应用函数(迭代)
对单一参数的函数
许多程序会涉及多次迭代的操作, Nest 和 NestList 是执行此操作的强大结构。
Nest[f,x,n] 将嵌套 n 次的函数 f 应用于 x
NestList[f,x,n] 生成列表 {x,f[x],f[f[x]],…},其中 f 的嵌套层深为 n
示例:
Nest[f, x, 4]
out:f[f[f[f[x]]]]
NestList[f, x, 4]
out:{x, f[x], f[f[x]], f[f[f[x]]], f[f[f[f[x]]]]}
如果我们不想指定重复几次,而是指定一个Boolean条件,则要使用NestWhile。
NestWhile[f,x,test] 重复应用函数 f,直到对结果应用 test 不再产生True
当然也有显示每步的输出结果的NestWhileList。
示例:
NestWhileList[Most, {a, b, c}, # != {} &]
out:{{a, b, c}, {a, b}, {a}, {}}
NestWhile[(#/2) &, 123456, EvenQ]
out:1929
还有一种循环条件的判定,就是将函数应用到函数值不再改变位置,即FixedPoint。
FixedPoint[f,x] 重复应用函数 f 直到结果不再更改
FixedPointList[f,x] 生成列表 {x,f[x],f[f[x]],…},当元素不再更改时停止
这个对于一些收敛问题十分常用。
二元函数的重复
FoldList[f,x,{a,b,…}] 创建列表 {x,f[x,a],f[f[x,a],b],…}
Fold[f,x,{a,b,…}] 给出由 FoldList[f,x,{a,b,…}] 生成的列表的最后一个元素
例如求累计和:
FoldList[Plus, 0, {a, b, c}]
out:{0, a, a + b, a + b + c}
使用Fold和FoldList可以在 Wolfram 语言中编写许多优雅高效的程序,例如实现FromDigits函数的功能:
digit[a_, b_] := 10 a + b;
fromdigits[digits_] := Fold[digit, 0, digits];
fromdigits[{1, 3, 5, 4, 6}]
out:13546
(*使用纯函数*)
Fold[10 #1 + #2 &, 0, #] &@{1, 3, 5, 4, 6}
out:13546
从上例中不难看出,使用纯函数能够写出高效简洁的函数。