Mathematica笔记·四
Mathematica第三原理:函数式操作
函数名作为表达式
在表达式如f[x]
中,函数名称f
本身也是一个表达式,可以像对待其他任何表达式一样对待。能够把函数名称像其他类型的表达式一样对待,源于Wolfram语言的符号式本质,这使得全范围的函数式操作成为可能
普通的Wolfram语言函数(如:Log
和Integrate
)通常操作的式数字和代数表达式等数据,而代表函数式操作的Wolfram语言函数不仅可以对普通数据进行操作,还可以对函数本身进行操作,例如:函数式操作InverseFunction
以Wolfram语言函数名称作为参数,来表示对函数的反函数
Wolfram语言中有很多种函数操作,有些代表数学运算,有些代表各种程序和算法,函数操作是从概念学习到实际应用Wolfram语言最有效的方法之一
重复应用函数
许多程序会涉及多次迭代的操作,Nest
和NestList
是执行此操作的强大结构
- Nest[f,x,n] 将嵌套
n
次的函数f
应用于x
- NestList[f,x,n] 生成列表
{x,f[x],f[f[x]],…}
,其中f
的嵌套层深为n
Nest
和NestList
可用于将函数应用固定的次数,通常,函数要应用到结果不在改变为止,这可以使用FixedPoint
和FixedPointList
来实现
- FixedPoint[f,x] 重复应用函数f直到结果不在改变
- FixedPointList[f,x] 生成列表
{x,f[x],f[f[x]],…}
,当元素不再改变时停止
同样的,我们也有对应用函数到结果无法满足所需条件为止的需求,可以使用NestWhile
和NestWhileList
实现
- NestWhile[f,x,test,m] 重复应用函数
f
,直到对m
(默认值是1,可选All
) 个最新结果应用test
不再产生True
- NestWhile[f,x,test,m] 生成列表
{x,f[x],f[f[x]],…}
,直到对m
(默认值是1,可选All
) 个最新结果应用test
不再产生True
Nest 等操作接受一个单参数的函数 f
,然后重复应用,每一步都将上一步的结果用作 f
的新参数,将这一概念推广至两个参数的函数是很重要的,你可以再次重复应用该函数,但现在获得的每个结果仅提供所需的新参数之一,一种简便的方法是在每个步骤中从列表的连续元素中获取另一个参数
- FoldList[f,x,{a,b,…}] 创建列表
{x,f[x,a],f[f[x,a],b],…}
- Fold[f,x,{a,b,…}] 给出由
FoldList
生成的列表的最后一个元素
使用 Fold
和 FoldList
可以在Wolfram语言中编写许多优雅高效的程序,在某些情况下,将 Fold
和 FoldList
视为由第二个参数索引的一组函数的简单嵌套可能会有所帮助
对列表和其他表达式应用函数
在像 f[{a,b,c}]
这样的表达式中,列表是函数的参数,但是很多时候我们需要将函数直接应用于列表的元素,而不是整个列表,这在Wolfram语言中可以使用 Apply
等函数实现
- Apply[f,expr,level] 将 f 应用于列表或表达式的指定层,
level
的值是列表,当指定层是第一层时可以简写成f@@@expr
,无指定层时默认是应用于表达式的顶层,可以简写成f@@expr
一般来说,Apply
的作用是指定的函数替换表达式的表头
对表达式的部分项应用函数
如果有一个元素列表,将函数分别应用于列表中每个元素通常很重要,在Wolfram语言中这可以使用 Map
完成
- Map[f,expr,level] 将
f
应用于表达式或列表的指定层中的每个元素,level
的值是列表,无指定层时或指定层是第一层时可以简写成f/@expr
- MapAll[f,expr] 将
f
应用于表达式的所有部分 - MapAt[f,expr,{part1,part2,…}] 将
f
作用于expr
中指定的项,其中指定项的索引(值是列表)是基于表达式的完全格式(可使用FullForm
查看完全格式) - MapIndexed[f,expr,level] 将
f
作用域表达式指定层的元素(level
未指定时默认作用于第一层),f
的第二个变量则使用对应项的指标号 - MapThread[f,{expr1,expr2,…},level] 将指定层中(
level
未指定时默认作用于所有)每个表达式中相同指标号的元素按顺序同时提供给f
注:Map
是作用于指定层,而MapAt
是作用于指定项,MapAll
则是作用于所有Map[f,expr]
实际上做的是将函数 f
封装在表达式 expr
的每个元素周围
注:可以设置选项 Heads->True
将每个部分的标头及其元素用f
封装
值得一提的是,如果我们需要对表达式中所有相同的项应用MapAt
,则可以使用Position
函数,将所有相同的项的位置提取出来
实际上:Map
是把一元函数作用于一个表达式的项上,而MapThread
是将多元函数同时作用于多个表达式
Map
等函数可以通过项的修改产生表达式,但有时不需要产生新的表达式,仅需要查看某些表达式,或者仅对表达式中某些项进行运算,一个典型的情况是当你所运用的函数具有某些“副作用”时,如进行赋值或产生输出时,我们则可以使用函数Scan
- Scan[f,expr,level] 求出
f
作用于expr
中第level
层项时的函数值(未指定时作用于各项)
注:当指定层是Infinity
时,Scan
会从最底层开始访问表达式中所有项
纯函数
- Function[{x1,x2,…},body] 纯函数中的
xi
可用任何变量替代 - body& 自变量为
#
或#1
、#2
、#3
等的纯函数
使用Nest
和Map
等函数运算时,总需要有一个确定函数名的函数去作用,纯函数可直接作用于变量,不需要函数名
Wolfram语言中的纯函数有多种形式,最理想的情况是定义一个目标函数,其作用于一个变量时给出函数值,当函数仅使用一次时,纯函数会比定义函数更加方便
熟悉形式逻辑和LISP编程语言的人会体会到Wolfram语言中的纯函数与λ表达式或无名函数相似,纯函数也类似于数学中的运算符
#
纯函数中的第一个变量#n
纯函数中的第n个变量##
纯函数中的所有变量列##n
纯函数中从n个变量开始的变量列
如果你不想再次提到该函数的话,函数名是无关的,因此同样的,一个纯函数中的变量名也是无关的,Wolfram语言允许用户不使用纯函数变量的显示名字,另一方面,我们可以通过给出“位置数字”#n
来指明变量,在一个Wolfram语言纯函数中,#n
表示所提供的第n个变量,#
表示第一个变量
当使用纯函数的简化形式时,千万不要忘记&
符号,否则Wolfram语言就无法理解和执行这一输入,在纯函数中使用&
符号时,要注意&
的优先级很低,必要时要用括号,这意味着,你可以不用括号输入形如#1+#2&
的表达式,另一方面,如果你愿意对纯函数设置选项的话,必须使用括号,如:option->(fun&)
,在Wolfram语言中,纯函数可以选择任意数量的变量,##
表示给定的任何变量,##n
表示从第n项开始的所有变量
用函数产生列表
除了前文中提及的函数NestList
及FoldList
,还有以下函数是常用的产生列表的函数
- Array[f,{n1,n2,…,ni}] 产生一个i维列表,其列表沿第i维度方向的长度由ni决定,其列表的每个元素是函数
f
作用与其对应标号所得到的值 - ComposeList[{f1,f2,…},x] 产生列表
{x,f1[x],f2[f1[x]],…}
这些函数在与纯函数结合的时候,可以构造一些非常有效的Wolfram语言程序
用函数选择表达式的项
有时,我们需要根据内容来选取元素,而不是根据位置选取,于是我们有函数Select
- Select[expr,f,n] 选择
expr
中的前n个(默认是全部)使函数f
的值为True
的项
在Mathematica笔记·二中的条件判断部分中介绍过一些Q函数,这些“判断词”常常用来作为Select
的依据
具有非符号头部的表达式
大部分情况下,我们希望像f[x]
这样的表达式的头部f
是一个简单符号,但具有非符号头部的表达式也有一些重要应用,我们可以将任何表达式作用头部,但其必须放在括号内,将一个复杂的表达式作用于头部的情况在“纯函数”节中已提到,将Function
作为表达式的头部时就定义了变量求值的函数
在Wolfram语言中有些类似于纯函数的结构,其用来表示数值函数等一些特殊的函数,在所有情况下,基本机制是给出能够包含所需要的函数完整信息的头部,如:
- Function[vars,body][args] 纯函数
- InterpolatingFunction[data][args] 由
Interpolation
和NDSolve
产生的近似数值函数 - CompiledFunction[data][args] 由
Compile
产生的编译数值函数 - LinearSolveFunction[data][vec] 由
LinearSolve
产生的矩阵解函数
复杂表达式作为头部的另一个重要应用是进行泛函运算
考虑一个求导运算的例子,在Wolfram语言中f'
用Derivative[1][f]
表示,“函数操作符”Derivativep[1]
作用于f
后给出另一个函数f'
算子运算
在前面的Map
中我们已经简单接触到了算子运算,即Heads->True
可以将表达式f[x]
理解为算子f
作用域表达式x
上,f[g[x]]
可以理解为算子f
和g
复合作用于x
上的结果
- Composition[f,g,h,…] 复合函数
f,g,h,…
,形如:f[g[h[…]]]
,可使用符号@*
表达 - RightComposition[…,f,g,h] 复合函数
f,g,h,…
,形如:h[g[f[…]]]
,可使用符号/*
表达 - InverseFunction[f]
f
的反函数 - Identity[expr] 恒等函数,即返回函数[expr]本身
- Through[p[f,g,…][x1,x2,…,],q] 当
q
与p
相同时(或q
缺省时),给出p[f[x1,x2,……],g[x1,x2,…],…]
- Operate[p,f[x],n] 在
f
的第n层(缺省值是1)运用p
吐个槽:Identity
的官方的应用示例过于奇怪,以至于我刚看见这个的时候蒙了半天
结构的操作
Wolfram语言有改变表达式结构的有效功能,这可以使我们实现合并,分配等数学特性,并对一些简洁和有效的程序提供基础,Wolfram语言函数Sort
不仅排列列表元素,也对具有任意头部的表达式排序,用这种方式,我们可以对任意函数实现结合特性或对称性,下面将列举一些常用于排序的函数:
- Sort[expr,pred] 用
pred
函数(缺省时使用默认方式)决定排列的方式 - Ordering[expr,n,pred] 用
pred
函数(缺省时使用默认方式)给出排好序后前n个(缺省时返回所有)元素的索引 - OrderedQ[expr] 当表达式
expr
的元素按照默认方式排序时返回True
,否则返回False
- Order[expr1,expr2] 按照默认排序方式时,
expr1
应该在expr2
之前时返回1,否则返回-1
除了排序操作,我们还可以对嵌套结构进行一些处理,可以用来实现一些分配特性和线性性质
- Flatten[expr,n,h] 压平头部是
h
(缺省时则对相同头部进行处理)的函数的嵌套结构,最多压平n层(缺省时压平所有层) - FlattenAt[expr,i] 压平表达式
expr
中的第i层结构 - Distribute[expr,g,f,gp,fp] 头部是
f
时(缺省值是expr
的头部),将f
分配到函数g
(缺省值是Plus
),并分别用fp
和gp
替代(缺省值是f
和g
)
一般的f
分配到“和式Plus
”时,时将如f[a+b]
的表达式“展开”为f[a]+f[b]
,函数Expand
对标准的代数乘法如Times
进行类似的分配展开,但Distribute
可以对任意运算进行类似的分配展开
吐个槽:我没看懂Distribute
到底咋用,似乎是将表达式内所有的元素排列组合一遍?
与Distribute
相关的函数Thread
,Thread
将函数并行的作用于一个列表或表达式的所有项
- Thread[f[args],g] 将
f
线性作用与头部为g
(缺省时作用于所有)的变量args
上
吐个槽:所以线性作用有没有更加好的解释方法?直接这么说我有点蒙,但是又看明白了
除此之外,我们还有一些结构上的常用操作
- Outer[f,list1,list2] 广义外积,
f
作用于每个元素上 - Inner[f,list1,list2,g] 广义内积,使用乘法算子
f
和加法算子g
像Distribute
一样,Outer
给出元素所有可能的组合,而Inner
像Thread
一样仅给出表达式中相应位置元素的组合
序列
当我们需要对函数中的元素进行拼接时,Sequence
用于实现这一功能
- Sequence[e1,e2,…] 将元素自动拼接到函数
吐个槽:这个函数就是全自动头部变换函数,头部是啥变成啥,并带上压平属性
本文于2024.9.9编写,于2024.11.1完成