二、表操作
二、表操作 返回目录页
1、引言
2、表的创建与表的测量
Range Table Length Dimensions
3、对表中元素的处理
Position Part Take Drop Delete First/Last/Rest Select/Reverse/Sort
RotateLeft/RotateRight Flatten Partiton Transpose Append/Prepend Insert/ReplacePart
4、对多个表的处理
Join/Union Complement/Intersection
5、高阶函数
Map/MapThread Outer Apply
6、函数对表的重复作用
Nest/NestList Fold/FoldList
7、字符串和字符
StringLength... Characters/StringJoin ToCharacterCode/FromCharacterCode
----------------------------------------------------
1、引言
上面可以看到,表操作函数有几十个。现在的MMA表操作函数,已经上千了。
最常用的,也就那么几十个,并不多。
我们熟悉MMA的函数,当从表操作函数开始。原因有几个:
表是MMA中最常用的数据结构。
表操作函数是最常用的MMA函数。
我们学英语,要先非常熟悉最常用的220个单词(Sight Words),一样,学MMA,要先熟悉这几十个表操作函数。
除此之外,还有个秘密原因,在本章的最后。如果急着想知道,就戳吧。
MMA中表之强大,在于表中可以放任何表达式。
List[2.4, dog, Sin, "read", {5, 3}, Pi, {}]
{}是空表,empty list。
List[2.4, dog, Sin, "read", {5, 3}, Pi, {}] // TreeForm
MMA中表之强大,更在于,表可以是多维的。这个维数,可以从树形结构上去理解。
----------------------------------------------------
2、表的创建与表的测量
表的创建,只有两个函数。表的测量,也只有两个函数。
表的创建的两个函数是:Range、Table
Range[1, 10, 2]
得:{1, 3, 5, 7, 9}
三个参数分别是:最小、最大、步长。相当于在构建等差数列。
Table[i, {i, 1, 10, 2}]
结果与上面一样的。可以看到,{i, 1, 10, 2}中的含义:变量、最小、最大、步长。
第一个参数i位置,可以放任何表达式。这个表达式可以与i有关,也可以与i无关。
函数设计到这里,似乎差不多了。但MMA展示了灵活性:很多参数是可以省略的。
A、如果步长为1,可以省略不写。
B、如果起始最小值为1,可以省略不写。
C、如果Table中的表达式与变量i无关,变量i可以省略不写。
D、终止最大值,永远不可以省略。
Range[5]
Table[2i,{i,5}]
Table["哈",{5}]
有没有找到好玩的感觉?MMA这是在偷工减料啊。。不过,很多程序员很喜欢这种偷工减料,因为很好用,而且代码很简洁。
Table[i + j, {j, 1, 4}, {i, 1, 2}]
Table[i + j, {j, 1, 4}, {i, 1, 2}] // MatrixForm
Table[i + j, {j, 1, 4}, {i, 1, 2}] // TableForm
Table函数还支持多重循环。外层循环变量(j)要先写。也就是说,循环变量在Table参数中的顺序是有讲究的。
Matrix是矩阵。这里一不小心,就构建出了一个矩阵。表作为最常用的数据,是因为可以构建很多东西。
表的测量的两个函数是:Length、Dimensions
很好理解,前者返回表的长度,后者返回表的维数。
Length[{1, a, b}] (*返回3,很好理解*)
Dimensions[{1, a, b}] (*返回{3},不好理解*)
因为表啊,可以是一维,也可以是多维。在多维时,可以是规则的多维(比如矩阵),也可以是不规则的多维。所以使情况复杂了。
我们来细看一下:
lis = {{{1, 2}, {3, 4}, {5, 6}}, {{a, b}, {c, d}, {e, f}}};
lis // TreeForm
lis // Length
lis // Dimensions
lis
Clear[lis];
lis =.;
lis
lis只是给输入的表起了个名字,可以理解为别名。用完了之后,最好取消别名。程序中给出了两种取消的方法。
给人家取了绰号,不能老叫啊。。老叫不礼貌。
取消了之后,lis就是一个符号,不再是表的别名了。如果后面一不小心又用了lis,就不容易出错。
观察树形结构,如果根节点是第0层,那么Length返回的是第1层的元素个数,这里是2。
Dimensions 返回的是"规则"列表的维数列表,这里是:{2, 3, 2}
意思是树形结构中,第0层节点均有2个分支、第1层节点均有3个分支、第2层节点均有2个分支。
所谓“规则”列表的意思是,每个节点上的分支数是要一样的。
那如果不一样会咋样呢?
Dimensions[{{a, b, c}, {d, e}, {f}}]
得:{3}
根节点下,有3个分支,OK,得3。然后第1层节点下分支数不同了,Dimensions就停止工作(输出),歇菜。
{{a, b, c}, {d, e}, {f}} // TreeForm
TreeForm很好用啊。。树形结构很好用。。
----------------------------------------------------
3、对表中元素的处理
处理?无非取得一些、添加一些、替换一些。还有一些是你想不到的,我们一一来举例说明。
---------------------------
Position[{a,7,a,2,4,4},a]
得:{{1}, {3}}
列表中的元素位置,是从1开始的,不是从0开始。
为啥不返回:{{1,3}} ?
请看:
Position[{{1,f,3},{4,5,f}},f]
得:{{1, 2}, {2, 3}}。f在第1行的第2列,和在第2行的第3列。
{1,2}这种形式,已经有人用过了。。
还有一个要注意一下:列表函数的返回值,经常以列表形式出现。因为列表函数经常嵌套使用,表中有表。。
---------------------------
Part[{1,2,f,4},3]
{1,2,f,4}[[3]]
抽取表中的第3个元素,这两种方式抽取,结果是一样的。
也就是说[[]]只是表面,在MMA内部,还是Part...还记得吧?MMA内部结构,都是函数(表达式)。
Part还有两个功能:
Part[{{1, f, 3}, {4, 5, f}}, 1, 2]
得:f
取得数组中的第1行、第2列元素。
在MMA中,数组经常指比较规则的表(用树形结构看,分支数相同),与C、Pascal中的数组概念不同的。
这个程序看起来,有点不太明显,用下面的是一样效果:
{{1, f, 3}, {4, 5, f}}[[1, 2]]
这样看起来,要好理解很多。
Part[{{1, f, 3}, {4, 5, f}}, {1, 2}]
{{1, f, 3}, {4, 5, f}}[[{1, 2}]]
这两句是等效的,猜猜看,得到什么结果?
既然如此,来个更复杂的:
{{1, f, 3}, {4, 5, f}}[[{1, 2}]][[{1, 2}]][[{1, 2}]][[1,2]]
这就是传说中的语法糖。
用好了,代码可阅读性急剧提高。
用差了,代码就不知所云了,变成恶搞了(比如上面这个)。
---------------------------
Take,可以前取、后取、连续取
Take[{1,2,3},1]
Take[{1,2,3},-1]
Take[{1,2,3},{1,2}]
Take[{1,2,3},{-3,-1}]
Take[{1,2,3},{-3,2}]
Drop,可以前删、后删、连续删
Drop[{1,2,3},1]
Drop[{1,2,3},-1]
Drop[{1,2,3},{1,2}]
Drop[{1,2,3},{-3,-1}]
Drop[{1,2,3},{-3,2}]
---------------------------
Delete,删除指定位置上的元素。
Delete[{1, 2, 3, 4, 5}, {{2}, {3}}]
得:{1, 4, 5}
Delete[{{1, f, 3}, {4, 5, f}},{1, 2}]
得:{{1, 3}, {4, 5, f}}
Delete要删除的,都必须特别指定位置。如果想连续删,用Drop。
---------------------------
First/Last/Rest,不言自明:
First[{1,2,3}]
Last[{1,2,3}]
Rest[{1,2,3}]
注意两点:First和Last返回的不是表,是表中的元素。
而Rest返回的是表。
---------------------------
Select/Reverse/Sort,又是不言自明的。
Select根据特定条件抽取表中元素。特定条件就是谓词,返回布林值,即不是真就是假。
Reverse是反转表。
Sort是排序。
Select[{1,2,3,4},EvenQ]
Reverse[{1,2,3,4}]
Sort[{2,1,4,3}]
与EvenQ类似的有很多,可以顾名思义:IntegerQ OddQ TrueQ PrimeQ ...
Sort默认是排出升序。但也可以指定为降序:
Sort[{4, 1, 3, 2, 2}, Greater]
在Greater位置,也可以放上排序函数。
---------------------------
RotateLeft/RotateRight
这两个函数,可以把表看成是可以转动的轮子。
可以指定转动步长。
RotateLeft[{1,2,3,4},1]
RotateRight[{1,2,3,4},2]
---------------------------
Flatten
可以把嵌套表,展平(flatten,或者翻译成压平更容易理解?)到各种不同的层次。
说到层次,又要用树形结构。
lis = {{{1, 2}, {3, 4}, {5, 6}}, {{a, b}, {c, d}, {e, f}}};
lis // TreeForm
Flatten[lis, 1]
% // TreeForm
Flatten[lis, 2]
% // TreeForm
lis =.;
观察输出结果,很容易理解。感性理解,就是一层一层剥皮,即把{}一层一层从外到内剥离。
那么不指定层次会如何?指定层次越大了会如何?可以玩一下:
lis = {{{1, 2}, {3, 4}, {5, 6}}, {{a, b}, {c, d}, {e, f}}};
lis // TreeForm
Flatten[lis]
% // TreeForm
Flatten[lis, 200]
% // TreeForm
lis =.;
答案揭晓:不指定层次,是一剥到底。层数过大时,也一样是一剥到底。
---------------------------
Partition
功能十分强大而复杂,这里只介绍常用的两个功能。
Partition[list,n]
将列表 list 分割成不重叠的具有长度 n 的子列表
Partition[Range[10],2]
Partition[list,n,d]
生成偏移量为 d 的子列表
Partition[Range[10],2,5]
---------------------------
Transpose
转置表中的前两层。
lis = {{a, b, c, d}, {1, 2, 3, 4}};
lis // Transpose
lis =.;
得:{{a, 1}, {b, 2}, {c, 3}, {d, 4}}
从表的角度看,是内部穿线一样。
从二维数组或者矩阵的角度看,如同行列转换:
lis = {{a, b, c, d}, {1, 2, 3, 4}};
lis // MatrixForm
Transpose[lis] // MatrixForm
lis // TableForm
Transpose[lis] // TableForm
lis =.;
---------------------------
Append/Prepend
添加元素到表的前面、后面。
Append[{1,2,3},4]
Prepend[{1,2,3},4]
---------------------------
Insert/ReplacePart
Insert[list,elem,n]
在 list 中的位置 n 上插入 elem。 如果 n 为负,位置从结尾计算。
Insert[{1, 2, 3}, a, 2]
Insert[{1, 2, 3}, b, -2]
可以观察到,当n为正数时,插入到指定位置前面。当n为负数时,插入到指定位置的后面。
ReplacePart[expr,i->new]
产生一个表达式,其中用 new 替换 expr 的第 i\[Null] 个元素.
ReplacePart[{1, 2, 3}, a, 2]
第2个位置的元素(2),被a替换掉了。
这是MMA较老版本(比如2.0版)的写法,现在也是支持的。较新版本(比如9.0版本)一般写成这样:
ReplacePart[{1, 2, 3}, 2 -> a]
效果是一样的。但新版本的写法可以扩充:
ReplacePart[{1, 2, 3}, {1 -> a, 3 -> c}]
这里第一次碰到转换符号:->,以后还会详细说的。
----------------------------------------------------
4、对多个表的处理
Join / Union
Join[{1,2},{3,4},{5,6}]
Join将数个表连接起来。另外,Join还可以指定层数连接,这里不举例了。
Union[{6,6},{3,4},{1,1}]
Union连接数个表之后,还把重复元素去掉,然后再排序。这有点集合操作的味道了,取并集。
因为Union有去重排序功能,当然也可以对一个表操作。但Union没有指定层数的功能。
Complement / Intersection
Complement求第一个列表中不出现在后面任何一个列表中的元素
Complement[{a, b, c, d, e}, {a, b, c}]
得:{d, e}
判断元素属于集合A、而不属于集合B。
(*
这里似乎缺少一个属于的判断函数。
可以使用Position函数与Length函数来做属于函数。
——想多了,可以F1这两个函数:Element MemberQ
*)
MMA似乎还缺少一个取补集的函数。且慢,如果把Complement函数的两个参数,看作表1与表2,
然后呢,把表1看作是全集,把表2看作是子集,那么Complement返回的就是补集!
继续来看Intersection函数。
Intersection[{1, 2, 1, 3}, {2, 1, 4}, {4, 1, 2, 3}]
得:{1, 2}
求交集。因为类似于集合运算,所以有去重排序功能。
如果用语法糖,直接写成这样,会更清楚:
{1, 2, 1, 3} \[Intersection] {2, 1, 4} \[Intersection] {4, 1, 2, 3}
----------------------------------------------------
5、高阶函数
有一些内置函数,把其他函数作为自己的参数,我们称之为:
高阶函数(higher order function)。
其实就是函数嵌套啦。
---------------------------
Map
这个函数可以将某一函数,作用于一个表的每一元素上。
Map[f, {3, 5}]
Map[Reverse, {{a, b}, {c, d}}]
Map[Sort, {{2, 6, 3}, {2, 4, 1}, {2, 3, 1, 4, 6}}]
MMA经常搞批发。这里显现出来了。
---------------------------
MapThread
多表元素,被分别组配到一起作为函数的参数。看例子:
MapThread[g, {{a, b, c}, {x, y, z}}]
MapThread[Power, {{2, 6, 3}, {5, 1, 2}}]
MapThread[List, {{5, 3, 2}, {6, 4, 9}}]
Transpose[ {{5, 3, 2}, {6, 4, 9}}]
最后两行的功能是一样的,Transpose的功能前面说过。
但是呢,Transpose只能有类似于行列转换的功能,而MapThread功能就强多了。
另外,MapThread还有指定层数的功能。
---------------------------
Outer
将把一个函数作用于几个表的元素的一切组合上。这是数学上外积(outer product)概念的一般化。
Outer[f, {a, b}, {2, 3, 4}]
Outer[List, {a, b}, {2, 3, 4}]
---------------------------
Apply
替换函数头。
Apply[f, List[1, 2]]
Apply[Plus, List[1, 2]]
---------------------------
这里多作点说明。
因为Map与Apply非常常用,所以有必要多了解一些。
Map[f, {3, 5}]
f /@ {3, 5}
Apply[f, List[1, 2]]
f @@ List[1, 2]
前后两句的功能是完全一样的,
Map的语法糖是:
/@ 分别作用于
Apply的语法糖是:
@@ 仅替换函数头
---------------------------
Map与Apply,均可以指定层数。
Map[f,expr] 或 f/@expr
将 f 应用到 expr 中第一层的每个元素.
lis = {{{1, 2}, {3, 4}, {5, 6}}, {{a, b}, {c, d}, {e, f}}};
lis // TreeForm
Map[f, lis] (*作用于第1层*)
Map[f, lis, {2}] (*作用于第2层*)
Map[f, lis, 2] (*作用于第1、2层*)
lis =.;
Apply[f,expr]
或 f@@expr 用 f 替换 expr 的头部.
lis = {{{1, 2}, {3, 4}, {5, 6}}, {{a, b}, {c, d}, {e, f}}};
lis // TreeForm
Apply[f, lis] (*作用于第0层*)
Apply[f, lis, {2}] (*作用于第2层*)
Apply[f, lis, 2] (*作用于第1、2层*)
lis =.;
---------------------------
附加说明函数的属性。
MapThread[Plus,{{1,2,3},{1,2,3}}]
得:{2, 4, 6}
直接写:
{1, 2, 3} + {1, 2, 3}
结果一样。
既可以被自动地转到表参数的各个元素上,又可以被自动地穿线于表参数的对应元素上的那些函数,我们称之为具有可作用于表的元素的(Listable)属性,即具有Listable属性。许多MMA内置函数都具有这一属性。
查看函数的属性,用Attributes函数:
Attributes[Log]
Log[{2, 3, 4}, {4, 9, 16}]
因为Log函数有Listable属性,所以有这种“穿线”功能。
---------------------------
Attributes[Plus]
得:{Flat, Listable, NumericFunction, OneIdentity, Orderless, Protected}
Flat, 可结合的,即服从结合律。
Listable, 前面已经说明,有“穿线”功能。
NumericFunction, 顾名思义,用于数值计算的函数。
OneIdentity, 函数对同一参数的重复作用是无效的,Plus[Plus[a + b]] // FullForm
Orderless, 无顺序的,即服从交换律。a+b与b+a一样的。
Protected, 受保护的。用户被禁止以任何显著的方法来修改这个符号。有点其他语言中保留字的味道。
----------------------------------------------------
6、函数对表的重复作用
这些函数,对做迭代非常方便。
---------------------------
Nest / NestList
Nest[f, x, 5]
得:f[f[f[f[f[x]]]]]
函数作用于参数,返回值作为参数,再被函数。。作用了5次。
NestList[f, x, 5]
得:{x, f[x], f[f[x]], f[f[f[x]]], f[f[f[f[x]]]], f[f[f[f[f[x]]]]]}
整个作用过程,给出了表。这样全过程就很清楚了。
ff[x_] := x + 1;
NestList[ff, 0, 20]
---------------------------
Fold / FoldList
在每一次迭代中,即函数返回值作为参数时,还另加表中的一个元素作为第二个参数。
Fold[f, 0, {a, b, c, d}]
得:f[f[f[f[0, a], b], c], d]
FoldList[f, 0, {a, b, c, d}]
将整个过程中,每个步骤所得,均取出来,作为表元素。
FoldList[Plus, 0, {a, b, c, d}]
FoldList[Plus, 0, {3, 5, 2, 4}]
FoldList[Plus, 0, Range[100]]
这个迭代过程很低效,只是为了说明Fold的迭代过程。1+2+3+...+100,最后得5050
---------------------------
Inner
被看成是数学中点乘运算(Dot函数)的推广。
Dot (.)
a.b.c 或 Dot[a,b,c]
给出向量、矩阵和张量的乘积,即点乘运算。
{a, b, c} . {x, y, z}
Inner[f, {a, b, c}, {d, e, f}, g]
得:g[f[a, d], f[b, e], f[c, f]]
f起到“穿线”组合参数的功能。而g起到最后的结合功能。
Inner[Times, {a, b, c}, {d, e, f}, Plus]
得:a d + b e + c f
Inner[List, {a, b, c}, {d, e, f}, Plus]
先是List起作用,产生三组表。然后是Plus起作用,用了“穿线”加法,最后得:
{a + b + c, d + e + f}
----------------------------------------------------
7、字符串和字符
字符串,在MMA中,是atom,最基本的数据类型。类似于一个值,不,就是一个值。
我们说,在MMA中,一切都是表达式。除了函数,就是atom。
字符串不是函数、更不是字符数组,是atom。
1 // FullForm
"test" // FullForm
"test" // InputForm
Length["mathematica"]
---------------------------
StringLength...
字符串很特殊啊、不是这个不是那个,更不是表。所以表操作函数,不能直接用于字符串。
坏消息啊。
不过,好消息是:一般的表操作函数,前面加上String,就可以用了:
StringLength["Mathematica"]
StringReverse["abcdefg"]
StringTake["abcde", 3]
StringDrop["ABCDE", -1]
StringPosition["abcde", "bc"]
...
可以看到,从原理上,把字符串作为字符列表来处理了。。
从学习上来说,我们不必从头到尾再学习一遍串操作函数了,因为与表操作函数太像了。。
一眨眼间,大部分串操作函数就理解了。
接下来,来几个针对串操作的固定有函数。
---------------------------
ToCharacterCode/ FromCharacterCode
ToCharacterCode["mathematica"]
% - 32
FromCharacterCode[%]
第一句,转化字符为ASC码。
第二句,上句输出表中每个元素减去32。
第三句,上句输出表中每个元素,转化为字符。
可以看到,小写都变成大写了。
因为字符与ASC码之间,可以轻松转化,那字符操作,就没有问题了。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
秘密就在于:
因为MMA中任何事物,都具有表达式这一共同结构,所以在表操作中学到的大部分内置函数,也可以用来对任何表达式(atom除外)操作。
清楚了吧?很多表操作函数,不是表专用的,其他表达式上都可以用。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
虽然这章有点长,但我们可以说,最常用的MMA函数,我们都掌握了。
MMA学习曲线上的重头戏,就这么愉快地上演完毕。
++++++++++++++++++++++++++++++++
扩展阅读:木有。如果有时间,把常用表操作函数拼写几遍,熟练一下函数功能。
这里有个mathematica 常用命令大全,按函数功能进行划分,有时候可以查阅。