【程序设计】5.6 [一把抓] 排序相关

 

5.6 排序相关

为了解决数字黑洞问题,我们已经写了好几种排序代码了。排序确实是很常见的动作,所以 SPL 直接提供了排序函数。

A.sort() 将返回将 A 的成员从小到大排列的序列。当然我们也会经常用到从大到小排序,使用 @z 选项就可以了。

排序函数返回成是序列原成员构成的序列,可以理解成某种选出函数,它也有个对应的定位函数,A.psort()将返回一个数列 p,使得 A(p) 是从小到大排列的。也就是说

A.sort()==A(A.psort())

请仔细理解一下这个等式的意义。

尝试一下,老规矩,A 列的运算结果就是 B 列的常数。


A

B

1

[13,30,45,23,42,98,61]


2

=A1.sort()

[13,23,30,42,45,61,98]

3

=A1.sort@z()

[98,61,45,42,30,23,13]

4

=A1.psort()

[1,4,2,5,3,7,6]

5

=A1(A4)

[13,23,30,42,45,61,98]

有了排序函数,数字黑洞可以更简单了,不需要用 pseg 和 insert 倒腾了:


A

B

C

D

1

5

12345

=A1.run(~=if(#>1,~[-1]*10,1))

2

for

=C1.(B1\~%10).sort()


3


=C1.sum(B2(#)*~)

=C1.sum(B2.m(-#)*~)

4


=@|B1

>output(B1)

>B1=B3-D3

5


if B4.pos(B1)

break

排序函数也支持有表达式的情况 A.sort(x),但它不等于 A.(x).sort(),它是由 psort 定义的,即 A.sort(x)==A(A.psort(x)),而 A.psort(x) 则可以用 A.(x).psort() 来定义。

这和 maxp 的情况是一样的,这是选出函数和定位函数的共同特点,select 也是这样,即 A.select(x)==A(A.pselect@a(x)),而不是 A.(x).select()。

不过,SPL 没有提供与 A.max(x) 风格一致、可直接返回 A.(x).sort() 的函数,这个需求就只能用这个表达式计算,这是历史习惯造成的,早期程序语言都是这么设计 sort 函数的。

排序一般是用于获得一个整齐的次序,但也可用来打乱一个序列的次序。


A

B

1

=to(1000)

=A1.sort(rand())

B1 的中的 rand() 函数在每次计算时会返回一个 0 和 1 之间的随机浮点数,随机的大小是乱的,那么排出来的次序也是乱的,结果原来很整齐的 A1 就被排成很乱的样子了。如果开发一个扑克游戏时,这个原理可以用来洗牌。

打乱次序的方法还可以用来抽样,比如要从 1 到 1000 的范围随机抽 100 个数,不能重复。简单地随机生成并不容易,如果发生了重复还要重新生成,比较麻烦。用上面这个排序方法就很简单,乱序后取前 100 个就行了。


A

B

1

=to(1000)

=A1.sort(rand()).to(100)

针对 rand() 函数多说几句,使用随机数用于生成测试数据是非常方便。但是,当测试时发现错误后,我们又希望能用同一套测试数据再跑一遍,以方便找出错误在哪里。然而,随机数就是会随机,再计算第二遍后又是另一批数了。

其实,计算机里并没有真正的随机数,而是在上一次生成的数进行一系列计算得出一个新数返回,当计算规则满足一些数学原理时,人就很难看出规律,而且也能保证算出来的数能均衡地分布在指定范围内,这种随机数称为伪随机数。只要第一个数确定了,再计算出来的数的序列都是确定的。

利用这个原理,我们就可以造出相同的“随机数”来了。


A

B

1

=rand@s(1)

=100.(rand())

2


=100.(rand())

3

=rand@s(1)

=100.(rand())

rand@s(s) 函数将把参数 s 设置为第一个随机数,称为种子。运行这段代码会发现,B1 和 B3 是一样的。

那么,如果每次执行程序开始时用到种子想关,那么生成的随机数序列不是都一样了吗?

不会的,程序启动时一般会自动执行一句类似 rand@s(long(now())) 这样的语句,now() 返回当时的时间,也就是用程序执行的起始时间作为种子,而这个是人操作出来的,有足够的随机性,这就能保证每次程序执行之前会得到不同的随机数序列了。

再回过头来看 psort,它并不是只用来把 sort 定义出来,它本身也有用处。考察这样一段代码:


A

B

1

=50.(1000+~)

=A1.(rand(100))

2

=B1.psort@z()

=A1(A2)

B2 中的 rand(n) 表示返回一个 n 之内的随机整数。

将这里的 A1 看成一批学生的学号(其实应该用姓名,但要等我们学过字符串知识后才会,现在就先用整数当学号),B1 理解为这些学生的成绩,和 A1 的成员一一对应,现在我们希望得到这批学号按其对应成绩排序后的结果序列。

对 A1 去排序显然是没有用的,而对 B1 排序能排出成绩本身,却得不到学号的成绩排序,使用 psort 就可以解决这个问题了,现在 B2 就是我们想要的按成绩排序的学号序列。

在成员数很多的时候,整个序列的排序就很慢,而且常常并没有业务意义。比如有几万个商店的销售额,我们通常只关心前几名是谁以及有多少销售额,没有兴趣关心上千名甚至上百名之后的情况了。而只想得到前几名,就没有必要对整个序列做排序。

SPL 提供了 top 函数用于只出前几名,类似地也还有 ptop 函数,但和 sort 函数的语法规则有点不同。因为 top 函数有着和 max 函数对应的的三种情况:返回值的 max、定位函数 pmax 以及选出函数 maxp。


A

B

1

[13,30,45,24,42,98,61]


2

=A1.ptop(3)

[1,4,2]

3

=A1.top(3)

[13,24,30]

4

=A1.ptop(3,~%10)

[2,7,5]

5

=A1.top(3,~%10)

[0,1,2]

6

=A1.top(3;~%10)

[30,61,42]

定位函数 A.ptop(n) 将返回最小的 n 个成员的位置,有参数时的 A.ptop(n,x) 定义为 A.(x).ptop(n),这个类似 pmax,不难理解。而 A.top(n) 则可以定义为 A(A.ptop(n)),在无 x 参数时,它即是值也是成员,类比为 max 和 maxp 都可以,不会有歧义。但是,有 x 参数时,A.top(n,x) 被定义为 A.(x).top(n),相当于 max,是针对值的。而选出函数则用 A.top(n;x) 来表示,注意其中是用来分隔参数的是分号而不是逗号,它和和 maxp 是对应的。

可能因为 topp 这个词实在不太好看而没有采用。这里的 n 不能省略,SPL 中也有大量用分号区分参数的情况(我们后面会再仔细讲),这样也就能区分开了。

总得来讲,SPL 中设计的函数风格还是比较一致的,各种不同运算之间容易类比。

【程序设计】 前言及目录

【程序设计】5.5 [一把抓] 定位选出

【程序设计】5.7 [一把抓] Lambda 语法 *