一些数据构成的有序集合即称为序列,构成序列的数据称为其成员。序列相当于高级语言中的数组,但其成员的数据类型不要求一致。下面通过生成、访问、运算符、函数等几个方面讲解序列的基本运算。
常数构造
在显示序列和序表时,在集算器中会显示Index列以便查看,这一列并不属于序列或序表的结构,在后面的展示中,并不一定会显示这一列的内容。
将常数直接用"[]"括起来可表示序列常数,也可以在表达式中用"[]"将成员括起来得到序列,如:
|
A |
B |
C |
1 |
1 |
red |
2013-06-04 |
2 |
2 |
blue |
27.49 |
3 |
3 |
yellow |
Tom |
4 |
[15.2,b,1] |
=[A1:C3] |
=[3,A4,B4] |
5 |
[1,2,3,3] |
[] |
[[]] |
网格中,A4,A5,B5,C5中,都是序列常数,B4和C4中是用表达式计算的序列。
其中,A4序列中的成员包含了浮点数、字符串、整数等不同的类型,B4中的成员由单元格区域得到,C4中序列的成员还包含序列,A5中序列中存在重复的成员。A4,B4,C4和A5中的数据依次如下:
下面来比较一下B5和C5中的值:
可以看到,B5中是空序列,而C5中是一个非空序列,其成员只有一个空序列。
说明:序列的成员可以是任意数据类型,包括基本类型、其他序列、记录等等,成员全是整数的序列被称为数列。
函数构造
|
A |
1 |
=to(2,6) |
2 |
="1,a,b,c".split@c() |
3 |
=periods@yx("2014-08-10",date(2018,2,1)) |
4 |
=file("sales.txt").import@t() |
其中A1表示从2到6的连续整数构成的序列,如果从1开始的数列可以简写为to(6)。A2中将字符串拆分为序列。A3中生成两个日期之间的日期序列,@y选项表示以年为间隔,@x选项表示不包括后端点。A1,A2和A3中的结果如下:
A4从结构化的文本文件中读取记录形成序列,其值为:
以记录为成员的序列又称作序表,常用来进行结构化数据的计算,序表不是本文重点,想进一步了解请参考使用序表与排列。
计算生成
下面网格中的代码,将文本文件sales.txt读入序表A1,取其中的STATE列,生成序列A2,将记录按照STATE分组,生成序列A3:
|
A |
1 |
=file("sales.txt").import@t() |
2 |
=A1.(STATE) |
3 |
=A1.group(STATE) |
计算后,A2中的序列如下:
A3中的序列如下:
可以看到,序列A3的成员是多个序列,这些序列的成员都是记录。
按序号访问成员
|
A |
B |
1 |
[a,b,c,d,e,f,g] |
|
2 |
=A1(2) |
=A1.m(2) |
3 |
=A1([2,3,4]) |
=A1(to(2,4)) |
4 |
=A1.m(-1) |
|
A2与B2中的表达式是等价的,都是从序列中取出第2个成员,结果如下:
A3中从序列中取出第2到4个成员,值为序列。注意,[2,3,4]也是个序列(数列),因此B3中中表达式的结果和A3中是相同的:
A4中取出序列中的倒数第1个成员。注意,倒取数时必须用A.m()函数,不能简写为A1(-1)。结果如下:
特别的,用A.m()函数可以访问多个成员,其中还可以指定某个区间,如:
|
A |
B |
1 |
[a,b,c,d,e,f,g] |
|
2 |
=A1.m(2,-2) |
=A1.m(2:4,3:5) |
3 |
=A1.m(-1,4:-2,5) |
=A1.m(-2:2) |
A2中获取序列中的第2个和倒数第2个成员,B2中获取序列中两段区间中的成员,A3中在获取序列成员时,同时使用了指定位置和区间的方式。A2,B2和A3中的结果依次如下:
B3中指定的区间是从倒数第2个到第2个,顺序是颠倒的,获得的结果中,成员的排列也是逆序的:
如果只需访问序列中的一段区间内的成员,可以使用A.to()函数,如:
|
A |
B |
1 |
[a,b,c,d,e,f,g] |
|
2 |
=A1.to(2,4) |
=A1.to(4,2) |
3 |
=A1.to(3) |
=A1.to(-3) |
A2,B2,A3,B3中的结果如下:
出B2的结果中可以看到,与A.m()不同,A.to()函数在指定区间时,可以由后至前,此时获得序列中成员的顺序也是逆序的。A3中表示获得序列的前3个成员,B3中表示获得序列的最后3个成员。
赋值和修改
|
A |
1 |
[a,b,c,d,e,f,g] |
2 |
>A1(2)="r" |
3 |
>A1([3,4])=["r","s"] |
4 |
>A1.modify(4,[ "r","s"]) |
为了明确每个单元格中的语句引起的变化,在这里点击工具栏中的按钮,分步执行代码。
A2将A1中的第2个成员修改为r,A3继续修改A1中的第3、第4个成员。A4从第4个成员起继续依次修改,该表达式等价于>A1([4,5])=["r","s"],逐步运行时A1中序列的变化如下:
新增成员
|
A |
1 |
[a,b,c,d,e,f,g] |
2 |
>A1.insert(0,"r") |
3 |
>A1.insert(2,["r","s","t"]) |
A2在序列结尾新增成员,A3继续在第2个成员之前连续插入3个新成员。仍然使用分步执行,A1中序列的变化如下:
除了insert之外,还可以用A.pad(x,n)函数,在序列A中连续添加x构成新序列,直到其中的成员个数达到n,如:
|
A |
1 |
[a,b,c,d,e,f,g] |
2 |
=A1.pad("A",10) |
计算后,A2中的结果如下:
A1中的原序列不会受到pad函数的影响。
删除成员
|
A |
1 |
[a,b,c,d,e,f,g] |
2 |
> A1.delete(2) |
3 |
> A1.delete([2,4]) |
A2中删除第2个成员,A3中继续删除第2、第4个成员,分布执行时,A1中变化如下:
集合运算
集合运算包括^(交集)、&(并集)、\(差集)、|(合集)等,比如:
|
A |
B |
1 |
[a,b,1,2,3,4] |
[d,b,10,12,3,4] |
2 |
=A1^B1 |
=A1\B1 |
3 |
=A1&B1 |
=A1|B1 |
A1与B1中的序列如下:
A2,B2,A3,B3中,分别计算这两个序列的交集,差集,并集和合集。计算后,A2,B2,A3,B3中的结果分别如下:
说明:并集与合集都是把两个序列的成员按顺序合并起来组成新序列,但并集中共同的成员不重复出现,合集中会重复出现。
在上面的集合运算中,序列中包含了不同种类的数据,既有字符,又有整数。双目运算只需比较是否相等,因此上面的代码能够正常运行,但需要注意的是,字符与整数是不能比较大小的。如果需要比较大小,如计算排序等,必须保证序列内成员数据能够比较。如:
|
A |
B |
C |
1 |
[3.456,5L,,2,-3,4] |
=["d","b","Ace",,"Tom","3"] |
[a,b,1,2,3,4] |
2 |
=A1.sort@z() |
=B1.sort() |
=C1.sort() |
A1中序列的成员包括各种实数及空值,B1中的成员包括字符串或空值,A1与B1中的成员均可以比较。A2与B2中排序后的结果如下:
其中,空值null都会被认为是最小值。
C1中的序列中,既包括字符串,又包括整数,两种类型的数据是不能比较的,因此C2在计算时会报错:
对位四则运算
长度相同的两个序列可按成员进行对位计算,返回序列,包括:++(加)、--(减)、**(乘)、//(除)、(求余)%%,比如:
|
A |
B |
1 |
[1,2,3,4] |
[10,12,3,4] |
2 |
=A1++B1 |
=A1--B1 |
3 |
=A1**B1 |
=A1//B1 |
A1与B1中的序列如下:
A2,B2,A3,B3中,分别用这两个序列计算对位加法,对位减法,对位乘法和对位除法。计算后,A2,B2,A3,B3中的结果分别如下:
布尔运算
在集算器中,用函数cmp(A,B)可以比较两个序列A与B的大小。
Ø cmp(A,B)
比较序列大小时,对位比较每个成员的值,遇到第一个不等成员时根据大小分别返回1或-1,A与B全等则返回0。特别的,cmp(A)或者cmp(A,0),表示A和与之等长且成员均为0的数列比较,即cmp(A,[0,0,…,0])。
|
A |
1 |
=cmp(["a","b","c"],["a","b","c"]) |
2 |
=cmp([1,3,5,7],[1,3,7,5]) |
3 |
=cmp([7,6,5,4],[7,6,4,10,11]) |
A1,A2和A3中结果如下:
两个序列的比较可以简写成A==B, A>B这样的形式。
通过使用这种写法,两个序列可以对位比较其大小,结果为布尔型。如:
|
A |
1 |
=[1,2,3]==[1,2,3] |
2 |
=[1,"B",3]<=[1,"b",4] |
3 |
=[1,2,3]<[1,3,4] |
A1中比较结果为true,两个序列相等。A2中比较结果为true,因为B比b小。A3中结果为true,因为两个序列的第2个成员比较时,2比3要小:
需要注意的是,集算器中的序列,是有序的集合,因此在判断两个序列A与B是否大小相等时,是跟顺序有关的。如果需要判断的是两个序列是否有着相同的成员,需要用A.eq(B)来判断:
|
A |
1 |
[Tom,Jerry,Tuffe,Tyke] |
2 |
[Jerry,Tuffe,Tom,Tyke] |
3 |
=A1==A2 |
4 |
=A1.eq(A2) |
A1和A2中成员的顺序不同,因此A3中的结果表明两个序列不相等:
A4中的结果为true,说明两个序列的成员相同:
序列与字串
通过s.split()和A.concat()两个函数,序列和字串可以很方便地相互转化。函数s.split(d)用来将字串s以分隔符d拆分成序列,@p选项自动识别数据类型;d缺省为按字符拆分。函数A.concat(d)用来将序列A用分隔符d连接,拼接成字串,自动处理数据类型;d缺省为无分隔符。在这两个函数中可以添加@c选项,表示以逗号为分隔符。
如:
|
A |
1 |
a,1,c,2011-8-11,false |
2 |
=A1.split@c() |
3 |
=A2.concat@c() |
A2和A3中结果如下:
可以看到,A2中拆分子串后,结果序列中的成员均为字符串,如果需要自动解析数据类型,可以添加@p选项。
正则表达式函数s.regex(rs)用正则表达式rs匹配字串s,返回匹配结果的序列;如果无法匹配,则返回null。regex函数中,可以添加选项@c表示大小写字母不敏感,添加选项@u表示使用unicode匹配。
用regex函数,最简单的用法就是判定某个字符串与指定正则表达式的匹配结果,下面先来看一下与数字字符串的匹配用法:
|
A |
B |
C |
1 |
="a12b".regex("(a[0-9])") |
="a12b".regex("(a[0-9]*)") |
="a12b".regex("^[0-9]b") |
2 |
="a12b".regex("\\S*([0-9][a-z])") |
'\S*([0-9][a-z]) |
="a12b".regex(B2) |
先来看A1,B1和C1中的结果:
在A1中使用的正则表达式是"(a[0-9])",其中a就是对应字母a本身,而[0-9]表示0至9之间的1个字符,即1位数字,两边的小括号()表示把字符串中能匹配“字母a开头后接1位数字”的串取出返回,结果就是单一成员a1构成的序列。B1中[0-9]后面的*表示连续匹配前面的字符任意次,这里表示连续的任意位数字,因此B1中返回的结果是a12。C1中,^表示字符串的开头,整个正则表达式需要返回数字开头后面接着字母b的字符串,但是a12b并非是“以数字开头”的,因此无法匹配,C1中的结果是null。
在A2中使用的正则表达式是"\\S*([0-9][a-z])",其中[a-z]表示字母a至z之间的字符,即所有小写字母。\S*表示连续的任意个可见字符,由于\是字符串中的转义字符,因此需要用\\S*表示。在这里,用小括号表示取出的部分是后面匹配[0-9][a-z]的结果,而前面匹配\S*的部分会被丢弃。在B2中,用字符串常数来表示A2中的正则表达式,此时不涉及字符串转义问题,所以C2中可以获得和A2中相同的结果。A2和C2中的结果如下:
在集算器中,用正则表达式匹配字符串时,需要将返回的部分用 () 括起来,将返回由括号括起来的成员所构成的序列,否则在能够匹配时只会返回字符串本身。
在使用regex() 函数时,可以添加@c选项,表示在匹配正则表达式时,大小写字母不敏感,如:
|
A |
B |
C |
1 |
="a12b".regex@c("(A[0-9])") |
="a12b".regex@c("([A-Z][0-9])") |
="a12b".regex("([A-Z][0-9])") |
A1,B1和C1中结果如下:
A1中正则表达式中要求匹配大写字母A,B1中要求匹配任1个大写字母,由于使用了@c选项,因此用regex() 都能得到匹配结果a1,而C1中未使用@c选项,无法匹配正则表达式,返回null。
用正则表达式rs匹配字串s,返回匹配结果的序列;如果无法匹配,则会返回null。
当用集算器处理非英文字符时,有时需要用unicode来执行正则表达式,这是由于用unicode比较标准,不会被字符集设置所干扰。此时使用regex() 函数时,需要添加@u选项,如:
|
A |
B |
1 |
="Gerente de Fábrica".regex(".* (.*á.*)") |
="Gerente de Fábrica".regex@u(".* (.*\\u00e1.*)") |
A1的正则表达式.* (.*á.*)中,小数点 . 表示除回车符和换行符外的任一个单字字符,.*则表示任意个字符,A1中取出的是从最后一个包含字符á的单词之后的字符串。B1中的正则表达式是同样的意义,不过regex函数添加了@u选项,其中的字符á用unicode的表示方法,表示为\u00e1,regex@u() 通常用于解析外部字符串,通过unicode设置正则表达式可以避免不同字符集的干扰。A1和B1中的结果是相同的:
聚合函数
序列的聚合函数包括求和A.sum()、平均值A.avg()、最大值A.max()、最小值A.min()、方差A.variance()等等。它们的使用方法都类似,如:
|
A |
1 |
[2,4,6] |
2 |
=A1.sum() |
3 |
=A1.sum(~*~) |
A2对序列求和,A3求序列中成员的平方和,A2和A3中的结果如下:
如果序列中的成员全部是结果为布尔型的判断条件,可以用函数A.cand() 和A.cor() 来判断这些条件是否全部成立,或者是否至少有一个成立。如:
|
A |
B |
C |
D |
1 |
=[1,2,3,4].cand(24%~==0) |
=[12,-3,0].cor(24%~==0) |
[] |
|
2 |
for 500 |
=A2%3==2 |
=A2%5==3 |
=A2%7==2 |
3 |
|
if [B2:D2].cand() |
>C1=C1|A2 |
|
A1中,判断序列[1,2,3,4]中的成员,是否全部是24的约数,B1中则判断[12,-3,0]中的成员是否包含了24的约数。中国古代有一道著名算题:“今有物不知其数,三三数之剩二;五五数之剩三;七七数之剩二。问物几何?”在第2、3行,计算了这道题目在500以内的解,记录在了C1中。计算后,A1,B1和C1中结果分别如下:
当序列中出现重复成员时,可以用不同的聚合函数A.count()和A.icount()来统计。如:
|
A |
1 |
[2,3,3,2,5,7,1] |
2 |
=A1.count() |
3 |
=A1.icount() |
A2中计算序列中所有成员的个数,而A3中只计算不同成员的个数,A2和A3中的结果如下:
如果序列中的成员都是整数,可以添加选项以提高计算效率,用A.icount@n() 来计算。如果序列中的成员都是整数或者长整数,则可以用A.icount@b() 来计算。
还有一些聚合函数是和排序相关的,如排序函数A.rank(y) 和中位数函数A.median(k:n)。如:
|
A |
B |
C |
1 |
[6,8,1,3,7,2,4,9,5] |
|
|
2 |
=A1.rank(8) |
=A1.rank@z(8) |
|
3 |
=A1.median() |
=A1.median(1:4) |
=A1.median(:3) |
A2中计算8在序列中从小到大的排名,B2中则计算8在序列中从大到小的排名,A2和B2中结果分别如下:
A3计算序列A1中的中位数,即序列中值的大小位于最中间的一个成员,如果序列中成员为偶数个,那么中位数将返回最中间的两个成员的平均值。如果在median函数中添加参数,则可以取出序列中成员按照从小到大的顺序,处于参数所指定分段点的成员,类似的,如果分段点正好处于两个成员中间,则会返回两个成员的平均值。如B3中即返回位于1/4点的数据,因此,A.median(k:n)的参数中,分段数n必须为不小于2的整数,而k必须小于。当参数中的k省略时,将返回序列n等分点位置的数据。A3,B3和C3中的结果如下:
还有一些函数是用来在多个序列之间聚合运算的,如:
|
A |
B |
1 |
[[1,2,3],[3],[3,4],[6,5,3]] |
|
2 |
=A1.conj() |
=A1.union() |
3 |
=A1.diff() |
=A1.isect() |
在使用A.conj(),A.union(),A.diff(),A.isect()这些函数时,应该是序列的序列。A2,B2,A3,B3分别计算A1中各个序列成员的和列、并列、差列和交列,结果分别如下:
循环函数
循环函数可以针对序列的每个成员进行计算,可以将结构复杂的循环语句用简单的函数来表达,包括循环计算、过滤、定位、查找、排名、排序等,如:
|
A |
B |
C |
1 |
[2,4,-6] |
=A1.(~+1) |
|
2 |
=A1.select(~>1) |
=A1.pselect@a(~>1) |
=A1.pos([-6,2]) |
3 |
=A1.ranks@z() |
=A1.sort() |
=A1.sort(-~) |
B1将序列中每个成员加1,结果如下:
A2过滤出大于1的成员,B2定位出大于1的所有成员的序号,C2查找成员-6和2在序列A1中的序号。A2,B2和C2中的结果如下:
A3中,通过在ranks函数中添加@z选项,按降序求得序列各成员的排名,B3将序列中的成员升序排序,C3则将序列中的成员降序排序。A3,B3和C3中的结果如下:
特别的,使用某些定位函数时,可以指定查找的起始位置,如:
|
A |
B |
1 |
[2,4,-6,null,4,3,] |
|
2 |
=A1.pselect@a(~>1, 5) |
=A1.pos(4,3) |
A2中,从第5个成员开始查找大于1的成员位置,B2中从第3个成员开始查找4的位置,A1和B2中结果如下:
在定位函数中,除了常用的@1和@z选项,还经常使用@0选项,如:
|
A |
B |
C |
1 |
[2,4,-6,null,4,3,] |
|
|
2 |
=A1.pselect(~>10) |
=A1.pselect@0(~>10) |
=A1.pos(5) |
3 |
=A1.pos@0(5) |
=A1.pmin() |
=A1.pmin@0() |
A2,B2,C2,A3,B3,C3执行后的结果如下:
可见,添加了@0选项后,A.pselect()和A.pos()函数在无法找到成员的情况下会返回0而不是空值,A.pmin@0()则会在序列中存在空值时返回首个空值所在的位置。@0选项还可以用于其它的一些定位或选出函数,如A.pfind(), A.ptop ()和A.top()。
除了@0选项,A.pselect()和A.pos()函数还可以使用@n选项,在无法找到成员的情况下返回序列的总长度+1,如:
|
A |
B |
1 |
[2,4,-6,null,4,3,] |
|
2 |
=A1.pselect@n(~>10) |
=A1.pos@n(5) |
A2和B2中的结果是相同的:
需要注意的是,@0选项和@n选项是互斥的,不能同时使用。