第 18 章 程序中的決策:條件塊

2018-02-24 15:51 更新

即使是像口袋里的手機(jī)這樣小型的電腦,也可以在短短幾秒鐘內(nèi)完成超過數(shù)千次的操作。更令人驚奇的是,它們可以基于內(nèi)存中的數(shù)據(jù)以及程序員編寫的邏輯進(jìn)行決策。這種決策能力在人們所思考的人工智能問題中是極為關(guān)鍵的要素,當(dāng)然也是創(chuàng)建有趣的智能應(yīng)用的重要組成部分。本章將探索如何在應(yīng)用中編寫判斷選擇邏輯。

{%}

正如我們在第14章所討論的,應(yīng)用的行為由一系列的事件處理程序所定義。每個(gè)事件處理程序針對某個(gè)特定事件進(jìn)行響應(yīng),并實(shí)現(xiàn)特定的功能。然而,這種響應(yīng)的過程未必是按線性順序來實(shí)現(xiàn)各項(xiàng)功能,有些功能只能在一定條件下才能執(zhí)行。像游戲類的應(yīng)用可能就會判斷分?jǐn)?shù)是否已經(jīng)達(dá)到了100,而位置感知類的應(yīng)用可能會問“某個(gè)手機(jī)是否在某個(gè)建筑物的范圍之內(nèi)”。你的應(yīng)用也可以詢問類似的問題,然后根據(jù)答案,繼續(xù)執(zhí)行不同的程序分支。

如圖18-1,當(dāng)事件(Event1)發(fā)生時(shí),無論如何A功能都會被執(zhí)行;然后進(jìn)行一個(gè)檢測判斷:如果檢測結(jié)果為真,則執(zhí)行B1分支;如果結(jié)果為假,則執(zhí)行B2分支;無論執(zhí)行哪個(gè)分支,該事件處理程序的其余部分(C)都將被執(zhí)行。

由于像圖18-1這樣的決策圖看起來像一棵樹,因此通常會將這種根據(jù)判斷結(jié)果而選擇執(zhí)行的一段程序稱為“分支”。在這種情況下,你會說, “如果測試結(jié)果為真,則執(zhí)行包含B1的分支?!?/p>

{%}

圖 18-1 事件處理程序中,根據(jù)條件測試的結(jié)果執(zhí)行不同分支

用if及ifelse進(jìn)行條件測試

App Inventor提供了兩類條件塊(如圖18-2):if塊和ifelse塊??梢詮腃ontrol抽屜里拖出一個(gè)if塊,然后點(diǎn)擊上面的藍(lán)色圖標(biāo),彈出可擴(kuò)充的塊,可以根據(jù)需要添加任意多個(gè)“else”分支。

{%}

圖 18-2 條件塊if及ifelse

可以將任何邏輯表達(dá)式(Boolean)插入到if右側(cè)的測試插槽中。邏輯表達(dá)式是一個(gè)用數(shù)學(xué)等式,它的返回值要么是真(true),要么是假(false)。如圖18-3,邏輯表達(dá)式使用關(guān)系運(yùn)算符(藍(lán)色)以及邏輯運(yùn)算符(綠色),對屬性值或變量值進(jìn)行檢測。

{%}

圖 18-3 用于條件判斷的關(guān)系及邏輯運(yùn)算符

無論是if塊還是ifelse塊,只有“if”后面的測試結(jié)果為真時(shí),將執(zhí)行“then”右側(cè)插槽中的塊。對于if塊,如果測試結(jié)果為假,程序?qū)⑻鰅f塊,繼續(xù)執(zhí)行if后面的塊;而對于ifelse塊,如果測試結(jié)果為假,將執(zhí)行“else”右側(cè)插槽中的塊。

因此,對于一個(gè)游戲來說,可能會插入一個(gè)與成績有關(guān)的邏輯表達(dá)式,如圖18-4所示。

{%}

圖 18-4 用于測試成績值的邏輯表達(dá)式

在本例中,如果成績到達(dá)100,則播放一個(gè)聲音文件。注意,如果測試結(jié)果為假,不執(zhí)行任何塊。如果需要在測試結(jié)果為假時(shí)執(zhí)行某些操作,可以使用ifelse塊。

編寫一段二選一的決策程序

考慮這樣一個(gè)應(yīng)用,無聊的時(shí)候也許會用到它:在手機(jī)上點(diǎn)擊一個(gè)按鈕,就可以隨機(jī)地?fù)艽蛞粋€(gè)朋友的電話。如圖18-5,使用一個(gè)random integer(隨機(jī)整數(shù))塊來生成一個(gè)數(shù)字,然后用ifelse對生成的數(shù)字進(jìn)行判斷,來決定即將撥打的電話號碼。

{%}

圖 18-5 用ifelse塊判斷隨機(jī)生成的整數(shù)來選擇要撥打的號碼

在這個(gè)例子中,random integer的參數(shù)為1和2,意味著將以相等的幾率產(chǎn)生1或2,所產(chǎn)生的隨機(jī)數(shù)保存在變量randomNum中。

一旦取得了變量randomNum的值,在ifelse塊中將變量值與1進(jìn)行比較:如果randomNum的值為1,程序?qū)?zhí)行第一個(gè)分支(then),將電話號碼設(shè)置為“111-1111”;如果變量值不為1,測試結(jié)果為假,程序執(zhí)行第二個(gè)分支(else),電話號碼被設(shè)置為“222-2222”。無論測試結(jié)果如何,程序都將拔打電話,因?yàn)槭窃谡麄€(gè)ifelse塊的下面調(diào)用了MakePhoneCall過程。

多重條件判斷

許多情況下不只是雙重選擇,即,可選擇的結(jié)果不僅僅是兩個(gè)。例如,也許你希望可以給更多的朋友隨機(jī)撥打電話,因此就需要在原來的else分支中,再加入一個(gè)ifelse,如圖18-6所示。

{%}

圖 18-6 外層條件判斷的else分支中加入另一個(gè)ifelse條件判斷

在這些塊中,如果第一個(gè)檢測條件結(jié)果為真,程序?qū)?zhí)行第一個(gè)“then”分支并撥打號碼“111-1111”;如果第一個(gè)測試結(jié)果為假,則執(zhí)行外層的else分支,此時(shí)將立即進(jìn)行另一個(gè)測試。因此,如果第一個(gè)測試結(jié)果(randomNum=1)為假,而第二個(gè)測試結(jié)果(randomNum=2)為真,則執(zhí)行第二個(gè)(內(nèi)層的)“then”分支,并撥打號碼“222-2222”;如果前面兩個(gè)測試的結(jié)果都為假,則執(zhí)行最下面的內(nèi)層的else分支,并撥打第三個(gè)號碼333-3333。

注意,在修改過的程序中,隨機(jī)整數(shù)生成器(random integer)中的參數(shù)2變成了3,因此,將以相等的幾率生成結(jié)果1、2或3。

這種在一個(gè)條件判斷中加入另一個(gè)判斷的方式稱為“嵌套”,在本例中,可以稱為“嵌套的if-else塊”,使用這種嵌套的邏輯,可以為隨機(jī)撥打電話的程序提供更多的選擇。一般來說,任何程序中都可以使用任意多層的嵌套。

復(fù)雜條件判斷

除了嵌套,還可以設(shè)定更為復(fù)雜的檢測條件,即,多于一個(gè)等式的檢測條件。例如這樣一個(gè)應(yīng)用,當(dāng)你(或你的手機(jī))離開某棟建筑或某個(gè)邊界時(shí),手機(jī)會發(fā)出震動(dòng)。這樣的應(yīng)用適用于那些受控人員,警告他們不要遠(yuǎn)離法定的邊界;也可以用于家長監(jiān)視孩子們的行蹤;教師可以用它來做自動(dòng)點(diǎn)名(條件是學(xué)生們都配有Android手機(jī)?。?。

例如,我們提出這樣的問題:手機(jī)是否在“舊金山大學(xué)哈尼科學(xué)中心”范圍內(nèi)?這樣的應(yīng)用要對4個(gè)不同的問題進(jìn)行一個(gè)復(fù)雜的檢測:

  • 手機(jī)所在的緯度低于邊界緯度的最大值(37.78034)嗎?

  • 手機(jī)所在的經(jīng)度低于邊界經(jīng)度的最大值(-122.45027)嗎?

  • 手機(jī)所在的緯度高于邊界緯度的最小值(37.78016)嗎?

  • 手機(jī)所在的經(jīng)度高于邊界經(jīng)度的最小值(-133.45059)嗎?

本例中使用了位置傳感器(LocatinSensor)組件,即便你沒用過這個(gè)組件,也能夠理解這些程序,在第23章中將有更多講解。

使用邏輯運(yùn)算符and、or及not可以構(gòu)造出更為復(fù)雜的測試條件,可以從Logic抽屜中找到它們。在本例中,先拖出一個(gè)if塊以及三個(gè)and塊,并將and塊放在if塊的測試插槽中,如圖18-7所示。

{%}

圖 18-7 放在if塊測試插槽中的“and”塊(選擇“External Input/外展式輸入”以免塊的排列過寬)

然后拖出幾個(gè)塊來組成第一個(gè)測試問題,并將其放在and塊的第一個(gè)測試插槽中,如圖18-8所示。

{%}

圖 18-8 and塊中放入了第一個(gè)測試問題塊

如法炮制出其他幾個(gè)測試條件,填入其他幾個(gè)and的測試插槽中,并將整個(gè)if塊放入事件處理程序LocationSensor.LocationChanged中,這樣就寫成了一個(gè)檢測邊界的程序,如圖18-9所示。

{%}

圖 18-9 每次位置更新時(shí),觸發(fā)該事件處理程序,來檢測是否在邊界之內(nèi)

這些塊的功能是,在每次位置傳感器讀數(shù)更新時(shí)做出判斷,如果手機(jī)的位置在邊界之內(nèi),則發(fā)出震動(dòng)。

OK,到目前為止,應(yīng)用已經(jīng)相當(dāng)酷了,但現(xiàn)在我們來嘗試更為復(fù)雜的功能,以便你能充分地了解程序中決策的威力。如何才能讓手機(jī)僅在越出邊界時(shí)才發(fā)出震動(dòng)呢?繼續(xù)學(xué)習(xí)之前,自己先想想如何來寫這樣的程序。

我們的方法是定義一個(gè)變量withinBoundary,目的是記住傳感器上一次的讀數(shù)是否在邊界內(nèi),并根據(jù)每一次后續(xù)讀數(shù)的測試結(jié)果對變量值進(jìn)行修改。withinBoundary是一個(gè)布爾(Boolean)類型的變量,與保存數(shù)字或文本的變量相比,它保存的值為true(真)或false(假)。舉例來說,如果將變量初始值設(shè)為false,如圖18-10所示,這意味著設(shè)備不在舊金山大學(xué)的哈尼科學(xué)中心范圍內(nèi)。

{%}

圖 18-10 變量withinBoundary為初始化為false

對塊做出修改,以便在每次位置信息變化時(shí),對變量withinBoundary進(jìn)行設(shè)置,并且只有當(dāng)手機(jī)越出邊界時(shí),才會發(fā)出震動(dòng)。說的更明確一些,手機(jī)產(chǎn)生震動(dòng)的必備條件是(1)變量withinBoundary的值為真,即意味著上一次讀數(shù)還在邊界內(nèi);(2)新的傳感器讀數(shù)超出了邊界。圖18-11中是修改后的塊。

{%}

圖 18-11 這些塊的功能是:只有當(dāng)手機(jī)從界內(nèi)移動(dòng)到界外時(shí),手機(jī)才會震動(dòng)

我們來仔細(xì)地分析一下。當(dāng)位置傳感器(LocationSensor)獲得讀數(shù)時(shí),首先判斷讀數(shù)是否在邊界內(nèi),如果是,將withinBoundary設(shè)置為true。由于我們希望只有在手機(jī)越出邊界時(shí)才震動(dòng),因此在第一個(gè)分支中不發(fā)生震動(dòng)。

如果執(zhí)行的是else分支,我們知道新的讀數(shù)已經(jīng)超出了邊界。此時(shí),我們需要檢查上一次的讀數(shù):盡管這次讀數(shù)超出了邊界,但我們希望僅當(dāng)上次讀數(shù)在邊界內(nèi)時(shí),才讓手機(jī)發(fā)出震動(dòng)。withinBoundary變量會告訴我們上一次的讀數(shù),因此我們會檢查這個(gè)變量,如果檢查結(jié)果為真,則讓手機(jī)震動(dòng)。

一旦確認(rèn)手機(jī)從界內(nèi)移動(dòng)到了界外,還有一件事必須要做,你能猜到是什么嗎?對,需要重新設(shè)置withinBoundary為false,這樣,在下一次收到傳感器讀數(shù)時(shí),手機(jī)才不會再次震動(dòng)。

關(guān)于布爾型變量,還有一點(diǎn)需要提示:檢查一下這兩個(gè)if測試,如圖18-12,它們的效果一樣嗎?

{%}

圖 18-12 你能說出這兩個(gè)if測試的結(jié)果一樣嗎?

答案是“一樣”!唯一的差別在于下邊的提問方式實(shí)際上更加老練,而上邊的測試還要將一個(gè)布爾型的變量(其值只能是true或false)與true進(jìn)行比較。如果withinBoundary的值為true,將true與true比較,結(jié)果一定是true;如果變量值為false,將false與true比較,結(jié)果為false。因此,只需要對withinBoundary的值進(jìn)行檢測,像右邊那樣,其結(jié)果相同,而且編碼更加簡潔。

小結(jié)

頭暈了嗎?尤其是最后的部分相當(dāng)復(fù)雜!但這類決策方法是高級應(yīng)用中必須具備的。如果你能一步一步(或者說一個(gè)分支一個(gè)分支)地實(shí)現(xiàn)這些行為,并做到邊做邊測試,我們敢斷言,你會發(fā)現(xiàn),即便是人工智能也不是不可能的。它讓你頭疼,也讓你的大腦獲得了些許邏輯思維的鍛煉,但無疑也是充滿樂趣的。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號