第18章 程序中的决策
即使是像手机这样的小型口袋电脑,也可以在1秒钟内完成数百万次的操作。更令人惊奇的是,它们可以基于内存中的数据以及程序员编写的逻辑进行决策。这种决策能力是解决人工智能领域问题的关键要素,当然也是创建有趣的智能应用的重要组成部分。本章将探索如何在应用中使用逻辑判断。
正如我们在第14章所讨论的,应用的功能由一系列事件处理程序所定义。每个事件处理程序针对某个特定事件进行响应,以实现特定的功能。然而,构成这些响应的指令未必都是按顺序执行的,有些指令仅在特定条件下才能执行。例如,游戏应用可能会判断玩家的分数是否已经达到100,而位置感知类应用可能会问“手机是否在某个建筑物的范围之内”这样的问题。你的应用也会遇到类似的问题,需要根据问题的答案决定执行哪个程序分支。
如图18-1,当事件甲发生时,无论如何都会执行指令A。然后进行一个判断。如果判断结果为真,则执行指令B1;如果结果为假,则执行指令B2。无论执行哪个分支,该事件处理程序都将执行其余的指令(C)。
由于像图18-1这样的决策图看起来像一棵树,因此通常会将这种根据判断结果而选择执行一段指令的程序称为“分支结构”。在这种情况下,你会说:“如果判断结果为真,则执行指令B1分支。 ”
条件判断
为了实现基于条件判断的分支结构,App Inventor提供了一个条件判断块:“如果...则”块。这是控制类代码块抽屉中的第一个块,可见其重要性!从这个块可以派生出更多的条件判断块。如图18-2所示,点击“如果...则”块左上角的蓝色标记,可以从该块中扩展出一个“否则”分支,以及任意多个“否则,如果”分支。
在“如果...则”块以及扩展的“否则,如果”块中,都有一个插槽,我们称之为“判断插槽”,可以将任何逻辑表达式插入到判断插槽中。逻辑表达式是一个数学方程式(等式或不等式),它的运算结果要么为真,要么为假。图18-3显示了App Inventor中的关系运算符(蓝色)以及逻辑运算符(绿色),逻辑表达式利用这些运算符对属性值或变量值进行比较判断。
当“如果...则”块判断插槽中的逻辑表达式结果为真时,执行该块中“则”部分的代码块;如果判断结果为假,则执行“如果...则”块后面的代码块。
对于游戏来说,用逻辑表达式来判断玩家的分数,如图18-4所示。
在这个例中,如果分数值大于100,即判断结果为真,则播放一段获胜的音效;如果判断结果为假,则不播放获胜音效,程序将跳到整个“如果...则”块之外,并继续执行“如果...则”块之后的代码。如果需要在判断结果为假时执行某些操作,可以使用“否则”或“否则...如果”块。
非此即彼的条件判断
考虑这样一个应用,无聊的时候也许会用到它:在手机上点击一个按钮,就可以随机地拨打一个朋友的电话。如图18-5所示,使用一个随机整数块来生成一个数字,然后用“如果...否则”块对生成的数字进行判断,来决定究竟拨打哪一个电话号码。
在这个例子中,随机整数块的参数为1和2,这意味着将以均等的几率产生1或2。所产生的随机数保存在全局变量随机数中。
一旦取得了变量随机数的值,在“如果...则...否则”块中将变量值与1进行比较:如果随机数的值为1,程序将执行第一个分支(则),将电话号码设置为“111-1111”;如果随机数不为1,判断结果为假,程序将执行第二个分支(否则),电话号码被设置为“222-2222”。无论判断的结果如何,程序都将拔打电话,因为对拨打电话功能的调用是在整个“如果...则...否则”块之外。
多重条件判断
在许多情况下,可供选择的结果可能不止两个。例如,也许你希望给更多的朋友随机拨打电话,因此需要在原来的“否则”分支前面添加入一个“否则,如果”块,如图18-6所示。
在上图的代码块中,如果第一个条件判断结果为真,程序将执行第一个“则”分支,并拨打号码111-1111;如果第一个判断结果为假,程序将执行“否则,如果”分支,并立即进行第二个条件判断。此时,已知第一个判断的结果(随机数=1)为假,如果第二个判断的结果(随机数=2)为真,则执行第二个“则”分支,并拨打号码222-2222;如果前面两个判断的结果都为假,则执行最下面的“否则”分支,并拨打第三个号码333-3333。
需要强调的是,这段程序之所以能够按照我们的设想运行,是因为随机整数块中的参数2被改为3,因此才能以均等的几率生成1、2或3。
你可以在最简单的“如果...则”块中添加任意多个“否则,如果”块, 甚至可以在一个条件判断中添加另一个条件判断。当一个条件判断被放在另一个条件判断的某个分支中时,我们称之为嵌套。不仅条件判断可以嵌套,在控制类代码块中的循环块(遍历数字循环、遍历列表循环及条件循环)也可以参与到嵌套中。从理论上讲,这种嵌套可以是任意多重,以便处理应用中更为复杂的逻辑。
复杂条件判断
条件判断的嵌套使用,解决了多种可能分支的问题,不过,逻辑的复杂性还有另外一个维度,即判断条件的复杂性。即便是在单个条件判断中,也可以设定更为复杂的判断条件。换句话说,判断的条件不仅限于一个等式,也可能是对多个逻辑表达式的综合判断。例如,有这样一个应用,当你的手机(很可能也是你)离开某栋建筑或某个地域的边界时,让手机发出震动。这样的应用适用于那些受管制的人,警告他们不要远离限定的区域。家长可以用它监视孩子的行踪,教师可以用它做课堂的自动点名(条件是学生们都配有安卓手机!),等等。
例如,我们提出这样的问题:手机是否在旧金山大学哈尼科学中心范围内?这样的应用要对4个不同的问题进行复杂的判断:
- 手机所在的纬度低于边界纬度的最大值(37.78034)吗?
- 手机所在的经度低于边界经度的最大值(-122.45027)吗?
- 手机所在的纬度高于边界纬度的最小值(37.78016)吗?
- 手机所在的经度高于边界经度的最小值(–122.45059)吗?
这个例子中用到了位置传感器组件。即便你没有使用过这个组件,也能够理解这些逻辑。关于传感器组件的详细内容将在第23章中讲解。
使用逻辑运算符“并且”(and)、“或者”(or)及“非”(not)可以构造出更为复杂的条件判断,可以从内置代码块的逻辑类抽屉中找到它们。在本例中,拖出一个“如果...则”块及三个“并且”块,并将其中一个“并且”块放在“如果...则”块的判断插槽中,另外两个“并且”块放在第一个“并且”块中。这样就从一个判断插槽扩展为四个判断插槽,如图18-7所示。
然后拖出几个块来拼成第一个判断条件,并将其放在第一个判断插槽中,如图18-8所示{![对图18-7中的后两个“并且”块点右键,选择“外挂输入项”,就变成图18-8中的样式。——译者注]}。
如法炮制出其他几个判断条件,填入到其余几个扩展的“并且”块插槽中,并将整个“如果...则”块放入位置传感器的位置信息改变事件处理程序中,这样就完成了一个判断设备是否在界内的程序,如图18-9所示。
这些块的功能是,每当位置传感器接收到新的位置数据时进行判断,如果手机的位置在设定的边界之内,则发出震动。
不错,能够做到这一点,已经很棒了,但现在我们来尝试更为复杂的功能,以便你能充分地理解程序中决策的威力。如何才能让手机仅在跨越边界时才振动呢?继续学习之前,自己先想想如何来写这样的程序。
我们的方法是定义一个全局变量“界内”,目的是记住传感器上一次收到的数据是否在边界之内,并与后续收到的数据进行对比。“界内”是一个布尔类型的变量,与保存数字或文本的变量相比,它保存的值只可能是真或假。举例来说,如果将变量初始值设为假,如图18-10所示,则意味着设备不在旧金山大学的哈尼科学中心范围内。
对块做出修改,以便在每次位置信息变化时,对变量“界内”进行设置,使得只有当手机越出边界时才会振动。说得更明确一些,手机产生振动的必备条件是:(1)变量“界内”的值为真,这意味着上一次收到的数据在边界之内;(2)传感器最新收到的数据超出了边界。图18-11中是修改后的块。
我们来仔细分析一下以上代码。当位置传感器收到数据时,首先判断数据是否在边界之内,如果是,将变量“界内”设为真。由于我们希望仅当手机越出边界时才振动,因此在第一个分支中不发生振动。
如果执行的是“否则”分支,我们知道新收到的数据已经超出了边界。此时,我们需要检查上一次的判断结果:尽管这次读数超出了边界,但我们希望仅当上次的数据在边界内时,才让手机振动。变量“界内”会告诉我们上一次对数据的判断,因此我们会对界内做出判断。如果判断结果为真,则让手机振动。
确认手机从界内移动到了界外之后,还有一件事必须要做,你能猜到是什么吗?对,需要重新设变量“界内”的值为假,这样,下一次再收到传感器在界外的数据时,手机才不会再次振动。
关于布尔型变量,还有一点需要提示:检查一下图18-12中的两个条件判断。它们的效果一样吗?
答案是“一样”!唯一的差别是,右边的手法更加老练,而左边的判断还要将一个布尔型变量的值(只能是真或假)与“真”进行比较。如果变量“界内”的值为真,将“真”与“真”比较,结果一定是“真”;如果变量“界内”的值为假,将“假”与“真”比较,结果为“假”。因此,只需直接对变量“界内”的值做出判断,像右边那样,不但结果相同,而且编码更加简洁。
小结
头晕了吗?尤其是最后的部分相当复杂!然而,在复杂的应用中,你无法避开这些复杂的逻辑。如果你能一步一步,或者说一个分支一个分支地实现这些功能,并做到边做边测试,毫无疑问你会发现,即使是处理像人工智能这类复杂的逻辑,你也是可以胜任的。它让你头疼,也让你的大脑获得了些许逻辑思维的训练,这无疑也是充满乐趣的。
注释
逻辑表达式:逻辑表达式也称为布尔表达式,通常包含三种要素:运算量(数字、字符、逻辑值、数学运算符、括号等)、关系运算符(<、>、=、≤、≥、≠)及逻辑运算符(非、并且、或者)。逻辑表达式的运算结果只有两个:真或假。例如,针对表达式:(x≥-1)并且(x≤1),它表示x的取值范围在-1到1的闭区间内。因此,当x=0时,表达式的运算结果为真;当x=2时,结果为假。