陈凯 上海市位育中学
用计算机程序对两个变量的数值进行比较是非常容易的事,但若要对三个变量的数值进行比较,虽然只是增加了一个变量,但是代码却要复杂很多,代码的编写方法也变得五花八门,这不免让人对三这个数字投去特别的目光。有研究复杂系统的学者指出,两个对象只能体现对比,而三个对象能产生交织,周期三蕴含着混沌。[1]
中文里有许多带数字三的词汇,有时候这个三表示多,如三番五次、三令五申、三姑六婆,有时候也表示少,如三言两语、三杯两盏,有时候还用来表示频繁,如接二连三、三天两头……三这个数字处在状态发生变化的交界线上,老子说,道生一,一生二,二生三,三生万物,那么为何是三生万物。有学者揣摩老子的想法,认为三这个数字能体现出阴阳二元对立中的激荡变化,是造成复杂的气化物生过程的初始值。[2]笔者认为,在对变量数值大小进行比较的任务中,三个变量数值相互比较的计算过程初步体现出复杂性,其中蕴藏的某些可识别的模式可能成为形式化地构造更为复杂的计算过程的开端,也可以作为三生万物在计算思维范畴上的解释。
要编写比较两个变量中数值大小的程序(假设变量值不相等),是相当容易的事情,Python代码片段如图1所示。
图1 打印两个变量中存有较大数值的变量名
对初学者来说,若是用如图2所示的嵌套分支结构的语句,来对三个变量中的数值进行比较,确认哪个变量中的数值最大(假设变量值不相等),很大的难点在于逻辑的推断过程。例如,应该先比较哪两个变量?比较完成后对不同的两个结果,又应该再比较哪两个变量?
图2 使用嵌套分支结构打印三个变量中存有最大数值的变量名
可以将变量想象成三个形状一样的盒子,而盒子中装有数量不等的球。当盒子与盒子碰撞时,装有更多球的盒子会闪光。那么,借助盒子闪光的状况,最多碰撞两次,就可以确定哪个盒子中球的数量最多了。设三个盒子为A、B、C,推理过程如下:将A盒与B盒碰撞,如A盒闪光,则将A盒与C盒碰撞,如A闪光则A盒中球最多,否则C盒中球最多;如果A盒与B盒碰撞后B盒闪光,则将B盒与C盒碰撞,如B闪光则B盒中球最多,否则C盒中球最多。这样的推理过程可以平滑地转换成高级语言中嵌套分支的代码。如果将上述三个盒子之间的比较换作三个人之间进行比武的场面,则更容易理解其中的判断过程。显然,相对于抽象的判断过程而言,一般人的头脑更擅长于具象的判断过程。
不过,如果是四个盒子或五个盒子,甚至更多的盒子,比较过程是怎样的呢?为了更清晰地看出比较过程的规律,可以将代码改成“if-elif”的结构,如图3所示。
图3 让判断条件更清晰的嵌套分支结构代码
将程序代码中进行比较的变量名单独列出,不管其比较含义,只看变量名的顺序和位置,就能发现其中存在某种模式:两个变量名并列,以及两个变量名前后交换并列,置放于左侧另起的第一层,然后取出并列的变量名中的首个变量名,再和第三个变量名并列以及交换,然后重复刚才的动作(如图4)。
图4 分支结构中的变量名变化模式
很显然,如果只有两个变量,是不可能归纳出任何变化模式的。然而,一旦领悟到三个变量的比较模式,对于更多变量,就可以一直套用这个模式进行比较,就仿佛是头脑中的三生万物。图5是对于四个变量找出存有最大数变量的模式和对应的程序代码。有趣的是,只需套用模式就能编写出正确的程序代码,而完全不用管其中涉及的逻辑原因。通过此模式,程序代码可以被自动构造出来,并经由测试被证明是可行的。
图5 使用嵌套分支结构打印四个变量中存有最大数值的变量名的模式和程序代码
想象有一个控制系统控制着机械手臂并通过盒子的碰撞来找出装有最多球的盒子,假设从a、b、c开始编号的许多盒子已经就位,而这个装置用机械手臂取出各个盒子进行比较,然后又将这些盒子放回原处,直到根据分支结构的流程找到了装有最多球的盒子。如果需要比较的盒子数量增加,那么需要改变的是控制系统的程序,而不需要改变盒子。
仔细揣摩上述嵌套分支结构中变量比较的模式,或者干脆将多个盒子之间的比较的场景换成多人比武的场景,发现存在这样的模式,是对应有现实上的原因的,上述变量比较模式变化的含义,实质上是将大小比较中“失败”的变量抛弃掉,而用“获胜”的变量与下一个变量进行比较。值得一提的是,笔者自己并不是直接领悟了此原因,而是从模式变化中猜想到了这个原因并通过运行代码验证了其可行性。
从这个本质原因出发,可以重新构造其他类型的比较模式,想象有一系列代表变量的盒子,每两个盒子比较后,将“失败”的盒子抛弃,这个过程可以用列表形象地展现出来,如图6所示。只有在存在三个盒子或更多盒子的情况下,这种“比较—抛弃”的模式才能被清晰地显现出来。
图6 比较两次并抛弃两次“失败者”
三个变量需要执行两次分支结构的判断,显然,可以推理知道n个变量需要n-1次分支结构的判断。因此,代码可以改成循环结构的形式,如图7所示。
图7 用循环结构比较并抛弃“失败者”
根据以上代码可以想象出如下图景:盒子被整齐地放成一排,有两只机械手臂抓起相邻盒子判断盒子内球的多少,并根据判断结果做出抛弃某一个盒子的动作,在这个过程中,只要剩下的盒子自动靠拢排整齐,那么机械手臂的行为方式就始终是一致的,控制机械手臂的控制系统无需因为盒子数量的增加而更换代码。不过,和上一小节比较过程形成鲜明区别的是,比较过程中盒子的数量会发生变化。
也可以设想另一种图景:存在一种可以“分身”的控制系统,它用一只机械手臂抓着一个盒子,而另一只机械手臂抓取的盒子是由这个控制系统的“分身”选取出的比较胜利者。这个图景在现实中显然是不可能存在的,但这个虚构的控制系统却能对应真正的计算过程,按此种图景可以编写出利用函数的模块化比较三个变量数值大小的程序代码,如图8所示。
图8 一种利用函数模块化比较三个变量数值大小的程序代码
回到嵌套分支结构寻找装有最多球盒子的问题,当盒子数量发生变化后,控制中心就需要修改控制的方式,不过,因为比较方式的变化本身也存在规律,所以就存在一种可能,可以另外设计一个能够修改此控制系统控制方式的上一层次的控制系统。但怎么实现呢?考虑到控制指令本身也可以是一种数据,那么只要对数据进行有规律的迭代,就能得到有规律的控制指令。图9所示的是一种比较指令生成的片段与运行结果,其中用数字1代表变量a,数字2代表变量b,以此类推,可以将此运行结果和图5中的变量比较变换模式作对比。虽然只是片段,但已经证明了用程序代码生成程序代码的可能性。程序代码中对存放比较指令的数据做了两次迭代,显然,迭代代码本身也存在着规律的变化模式,还可以进一步用循环结构使迭代过程更加自动化。
图9 按变化模式生成比较指令的程序代码
勉强用语言来描述这个多层次的控制系统的运行方式:一个内部控制系统根据已有的存储区域中盒子里的球的数量,有规则地按替换模式更换盒子。而所更换的盒子里的球的数量也就是数据,则成为外部控制系统进行比较时抓取哪一个盒子的依据。
本文重点不在编程语言,也不在算法,而是算法在某些特定环境条件约束下的变化模式,图景想象的方法是一种有用的工具,它将人从用算法解决实际问题的思维框架中拉出,借助复杂性的思维来思考算法本身可能存在的变化模式。