[python] [VI coding] 第五章 條件與遞迴 - 教學區

[python] [VI coding] 第五章 條件與遞迴

文章瀏覽次數 5351

特種兵

特種兵圖像(預設)

2020-09-19 16:51:43

From:1.161.149.22

第五章 條件與遞迴

5-1 取餘數運算子

比較常見的運算子除了之前提過的 +, -, *, / 四則運算以外,在程式中通常會有一個取餘數的運算子。

在 python 中是 % (百分比) 符號,它的語法規則和其他的運算子一樣,功能則是取得第一個整數除以第二個整數的餘數。

在數學中沒有這種運算子,所以我們特別提出來說明。

>>> quotient = 7 // 3
>>> quotient
2
>>> remainder = 7 % 3
>>> remainder
1

所以,7 除以 3 是 2 餘 1

這邊我們用了取整數的除法運算子 // 才不會有小數。

取餘數非常實用,例如判斷 a 取餘數 b 若為 0 ,則表示 a 整除 b。

利用取餘數可以很快地取得右邊位數的數字,例如 a % 100 可以取得 a 的十位與個位數(如果有的話)。

5-2 布林運算式

布林運算式使用 == (比較運算子,也有人稱為關係運算子) 來判斷一個式子的結果。

結果為真 (True) ,則條件成立,執行你想做的事情;反之,結果為假 (False) ,則條件不成立,執行不成立時想做的事情,或者什麼都不做。

>>> 5 == 5
True
>>> 5 == 6
False

True 跟 False 是兩個特別的值,它們屬於 bool (boolean) (布林資料形態),不是字串,同時它們的首字母必須是大寫。

>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>

如果我們使用全小寫,會發生錯誤:

>>> type(true)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'true' is not defined

python 以為我們有個叫做 true 的變數,但實際上沒有定義,所以它找不到而回報錯誤。

== 是比較運算子的其中之一,列出其他的如下:

  • x != y # x 不等於 y,不等於又可寫成 <>
  • x > y # x 大於 y
  • x < y # x 小於 y
  • x >= y # x 大於或等於 y
  • x <= y # x 小於或等於 y

這些符號都很容易理解,但經常發生的錯誤是把 == 寫成 =

兩個等於是比較運算子,一個等於是指派運算子。

比較運算子用來比較兩個運算元之間的關係,而指派運算子是賦右邊的值給左邊的運算元。

>>> a = 3
>>> b = 5
>>> a != b
True
>>> a == b
False
>>> a
3
>>> b
5
>>> a = b
>>> a
5
>>> b
5
>>> a == b
True
>>> b = 10
>>> a
5

a 雖然等於 b, 但並不會因為 b 後來的值改變而跟著改變。

5-3 邏輯運算子

有三個邏輯運算子分別為 and (且,也就是和) or (或) 與 not (非,也就是否、不是)。

它們的意思也很明確,例如: a > 0 and a < 10 結果為真,

那麼 a 的值只可能介於 0 和 10 之間,也就是 1 到 9 的其中一個數。(這邊先假定 a 只能是正整數)

因為 0 不大於 0 ,且 10 不小於 10 ,所以 0 跟 10 都不在 a 的範圍當中。

又如: n % 2 == 0 or n % 3 == 0 結果為真,表示 n 可能整除 2 也可能整除 3,甚至兩者都整除也可以。按照順序,當有一個條件為真,則結束判斷。

最後, not 的例子,not (a < 10) 條件為真,就表示 a >= 10, 可以把 not 想成 相反 的意思。

對 python 來說,比較運算式不是很嚴格,也就是非零的值,都會被當成 True 來看待。

>>> 17 and True
True

有時可以善用這種靈活性,但你必須了解你在做什麼,否則就避免這樣的用法。

5-4 條件的執行

為了寫出有用且靈活的程式,我們經常需要運用條件判斷與執行相應的敘述,或改變程式的行為。

最常使用的就是 if (如果) 敘述:

if 條件:
	執行程式

我們看一下實際的例子:

if x > 0:
	print(x, '是正數')

如果 x 大於 0 條件為真,就執行後面縮排的敘述,否則什麼事都不會發生。

if 的敘述跟函數定義的結構相似,具有一個標題與一個縮排的程式區塊。

縮排的程式碼沒有限制多少行的數量,但至少要有一個敘述,你也可以使用 pass 來跳過 if 敘述,它不會做任何事。

pass 通常用來架構程式碼,類似虛擬碼 (pseudo code) 的作用。

或者 if 區塊內還沒想好裡面完整的程式碼,但已經知道要完成什麼事了,所以可以先放一段註解進去,之後再把程式碼填上。

if x < 0:
	pass  # 需要處理負數行為

5-5 替代執行

if 敘述的第二種形式是二擇一的,同時程式只會執行一個區塊,像這樣:

if 條件:
	執行程式
else:  # 其他條件
	執行程式

直接看實際的例子:

if x % 2 == 0:
	print(x, '是偶數')
else:
	print(x, '是奇數')

當 x 可以整除 2 就表示它是偶數,否則為奇數。程式會印出相應的訊息。

替代執行可以說是二擇一的條件執行,隨著條件,只可能發生一種結果,因為真與假本來就是對立的兩面。

這種結構又被稱為 分支敘述,類似樹狀的結構。如果出現這樣的敘述,無論如何一定會有其中一部分被執行。

5-6 帶狀條件

有時我們會需要兩種以上的條件來處理可能的情況,這就是 帶狀條件

if 條件1:
	執行程式
elif 條件2:
	執行程式
elif 條件3:
	執行程式
else:  # 其他條件
	執行程式

看一下實際的例子:

if x < y:
	print(x, '小於', y)
elif x > y:
	print(x, '大於', y)
else:
	print(x, '和', y, '是相等的')

elif 就是 else if 的縮寫。它可以說是一個 if 敘述的分支,沒有限制它的數量。

事實上 elif 或 else 在 if 敘述中是非必要的。但如果有 else 的話,一定要放在最後。

if choice == 'a':
	drawA()
elif choice == 'b':
	drawB()
elif choice == 'c':
	drawC()

當第一組 if 條件為假時,繼續測試 elif 的條件,直到一個條件為真,則執行該條件下之縮排敘述。

如果沒有 else ,則整個帶狀的 if 條件可能都不會被執行,但若有 else 條件,則該帶狀條件必有一個敘述會被執行。

當一個條件為真時,執行完其縮排程式碼區塊後,就會離開整個帶狀的 if 條件。

如果有多個條件為真,則按順序只執行第一個為真的條件。

沒有 if 就不會有 elif 或 else, 它們不會先於 if 出現。

5-7 巢狀條件

一個條件可以放於另一個條件之中,就是所謂的 巢狀條件,又稱嵌套條件。例如:

if x == y:
	print(x, '與', y, '相等')
else:
	if x < y:
		print(x, '小於', y)
	else:
		print(x, '大於', y)

首先有兩個條件,一個是 x == y ,另一個是其他狀況。

當其他狀況發生時,又分成兩個條件,也就是大於或小於。

如果可以的話,少用太多層的巢狀條件,因為它看起來不能快速被理解。而且最好要加註解,讓閱讀者知道這段判斷是做什麼用的。

接下來是一個改寫巢狀條件的例子:

if 0 < x:
	if x < 10:
		print(x, '是個正個位數')

可以改寫成:

if 0 < x and x < 10:
	print(x, '是個正個位數')

對於這種條件,python 有個更簡潔且更像數學式的寫法如下:

if 0 < x < 10:
	print(x, '是個正個位數')

另外,當判斷條件是真的狀況,可以這樣縮寫判斷式:

is_num = True
if is_num:
	print('它是真的')
else:
	print('它是假的')
print('程式結束')

上例中,最後一行程式無論如何一定會被執行到。

5-8 遞迴

在前面的章節中,我們知道函數中可以呼叫另一個函數,當然函數也可以呼叫自己本身,這就叫做「遞迴」。

它可以讓我們做出很神奇的事,例如這個函數:

def countDown(n):
	if n <= 0:
		print('爆炸')
	else:
		print('還有', n, '秒會爆炸')
		countDown(n - 1)

當 n 小於或等於 0 時印出爆炸,否則就印出還有多少時間會爆炸,並且以 n - 1 作為參數,再次呼叫自己這個函數。

我們像這樣調用這個函數:

>>> countdown(3)

函數的執行過程如下:

  • 當 n 是 3, 它大於 0, 因為符合 else 的條件,所以印出還有 3 秒會爆炸,並以 3 - 1 ,也就是 2, 帶入自己這個函數。
  • 當 n 是 2, 它大於 0, 因為符合 else 的條件,所以印出還有 2 秒會爆炸,並以 2 - 1 ,也就是 1, 帶入自己這個函數。
  • 當 n 是 1, 它大於 0, 因為符合 else 的條件,所以印出還有 1 秒會爆炸,並以 1 - 1 ,也就是 0, 帶入自己這個函數。
  • 當 n 是 0, 它小於等於 0, 因為符合 if 的條件,所以印出爆炸並結束函數。

執行結果像這樣:

還有 3 秒會爆炸
還有 2 秒會爆炸
還有 1 秒會爆炸
爆炸

這就是 遞迴。再舉一個例子,我們寫一個印 n 次變數 s 的遞迴函數:

def printN(s, n):
	if n <= 0:
		return
	print(s)
	printN(s, n - 1)

當 n 小於或等於 0 時,使用 return 結束函數,並返回 __main__,否則就印出變數 s 的值,並再次以 n - 1 為參數呼叫自己這個函數,流程與上個例子類似。

雖然這個例子使用迴圈會更容易撰寫,但後面我們會發現,有些情況使用遞迴比較好寫,因此先把重點放在學習遞迴上面。

5-9 無限遞迴

如果一個遞迴函數最後一直沒有回到 __main__ 或結束函數的基本敘述,就會讓函數一直呼叫自己。

這當然不是個好主意,我們稱這種情況為 無限遞迴

def recurse():
	print('一直印,不要停')
	recurse()

它真的會一直印,不要停。因為我們沒有給它停止的條件。或者說,我們沒有讓結束的條件發生。

不過幸好在 python 中不會真正無限地執行遞迴函數,而是當遞迴的深度到達極限時,停止程式並報錯。

類似底下的狀況:

File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
RuntimeError: Maximum recursion depth exceeded

它可能會執行一段時間才終止並報錯,例如已經有 1000 個遞迴函數框架在系統中,使用遞迴時切記不要發生這種情況。

5-10 鍵盤輸入

到目前為止,我們的程式還沒辦法接受使用者輸入的資訊,都是我們以自己擬定的值作為參數來運作。

在 python 中提供 input() 函數來等待使用者輸入,按下 enter 結束輸入,並將使用者輸入的資訊以字串型態回傳。

>>> text = input()
打字測試
>>> text
打字測試

讓我們改善上面的範例,在畫面上直接提示使用者應當輸入什麼資料是個好做法,input() 接受一個字串參數作為提示語。

>>> name = input('請輸入你的大名:\n')
請輸入你的大名:
python
>>> name
python

\n 是一個換行符號,這也就是為什麼我們會在提示字串的下一行進行輸入的原因。

如果你希望收到的值是整數,那你可以轉換資料型態:

>>> num = input('請輸入整數:')
請輸入整數: 100
>>> num
'100'  # 有引號是個字串
>>> int(num)
100  # 轉成整數

上面使用了型別轉換,但如果使用者輸入的是非數字的字串,則會發生錯誤:

>>> num = input('請輸入整數:')
請輸入整數: abc
>>> int(num)
ValueError: invalid literal for int() with base 10

後續我們將介紹如何處理這樣的錯誤狀況。

總之,一定要記住,當使用者輸入後回傳的值,一定是一個字串。

除錯

當程式一次輸出很多錯誤訊息時,我們可能會不知如何是好,把握兩個重點:是什麼樣的錯誤?發生在什麼地方?也就是英文的 what 與 where.

通常語法錯誤比較容易發現,遇到縮排問題修改起來會比較麻煩,但對使用導讀軟體的視障者,可能是個例外。

這部分我們在前面的章節有討論過,看一下這個例子:

>>> x = 5
>>>	y = 6
File "<stdin>", line 1
	y = 6
	  ^
IndentationError: unexpected indent

錯誤指向 y ,但實際上是前面的 tab 符號縮排導致的,而且行號顯示為第一行。

通常 python 提示我們的錯誤之處是參考用的,所以在除錯時,我們應該針對系統給的錯誤點,上下擴大一些範圍來查找錯誤的根源會比較好。

另外,監視變數或函數的回傳值也有助於查找錯誤的根源。

下面是一個可能發生錯誤的程式,讓我們測試看看並修改它。

def func(n):
	if n > 0:
		print(n, '大於 0')

num = input('請輸入一個數字 ')
result = num - 10
func(result)

動動腦

1.如果沒有取餘數運算子的話,那我們有什麼辦法取得餘數呢?

def getMod(x, y):
	m = x - (y * (x // y)) 
	print(m)

getMod(12, 7)

想法來自於: 被除數等於除數乘以商加餘數

2.寫一支程式,可以顯示現在的時間,並根據現在的時間打招呼,中午十二點前顯示早安,中午十二點後顯示午安。

from datetime import datetime as dt

nt = dt.now()
print('現在的時間是:', nt)
if nt.hour < 12:
	print('早安')
else:
	print('午安')

3.短判斷

一般的 if-else 判斷在上述章節提過,事實上,各種語言都有所謂短判斷的寫法。

短判斷又稱三元運算,就是由三個運算元構成的判斷式。

短判斷可讓程式更簡潔,在使用上跟列表表達式一樣,但條件複雜時,還是使用一般的寫法會比較好維護與寫註解。

例如,不要使用嵌套,也就是巢狀的短判斷,因為這樣就不短了。

以下是短判斷的範例:

s = '大於' if a > b else '不大於'

if 左邊是條件為真所執行的敘述,else 右邊是條件為假執行的敘述,而 if 和 else 之間則是判斷條件。

當 a 大於 b 時, s 就是 '大於' 字串,否則 s 為 '不大於' 字串。

有些程式語言的短判斷是用符號,在 python 還是使用原來的 if-else ,這樣似乎比較一致且容易理解。

短判斷也可以直接用在 print 裡面,以下是個範例:

print('猜對了' if guess == puzzle else '你輸了')

也可以搭配 format() 使用:

num = int(input('輸入整數: '))
print('{0} 為 {1}'.format(num, '奇數' if num % 2 else '偶數'))

有時候的確還滿方便的,但記得不要試著去把複雜的 if 巢狀判斷式改成短判斷,

這樣只會增加維護的難度,對於一起寫程式的夥伴來說,不是件好事。

你也不應該因為可以把複雜的判斷式改成短判斷而感到高興,因為過一段時間後,自己可能也會看不懂。

總之,一件事情沒有絕對,但至少出現這樣的語法時,我們能讀懂它。

練習

第1題 檔名 5-1.py

撰寫一個名為 guess 的函數,接受兩個參數,分別是 ans 與 num。

  • 當 num 大於 ans 時,顯示「猜小一點」
  • 當 num 小於 ans 時,顯示「猜大一點」
  • 當兩者一樣時,顯示「猜對了」
第2題 檔名 5-2.py

任兩邊長度的和必須大於第三邊,且任兩邊的差必須小於第三邊,滿足以上兩個條件的三邊才能組成三角形;

(1)寫一個名為 isTriangle 的函數,接受三個參數作為三邊長,功能是如果這三個參數可以構成三角形,則印出 ok,否則印出 no

(2)寫一個程式讓使用者輸入三個數字,並轉成整數,再利用 is_triangle 函數確認是否可以構成三角形。

第3題 檔名 5-3.py

寫一個可以找錢的函數。

(1) 讓使用者輸入一個數字代表多少錢。

(2) 印出你目前有多少錢。

(3) 接著印出可以換成多少零錢(最少零錢方案),還剩多少零錢。

(4) 換錢的功能需要寫成一個函數。

(5) 假設零錢沒有一個二十塊的。

執行結果範例:

請輸入多少錢: 127

你目前有 127 元

可以換成:

50 元 2 個

10 元 2 個

5 元 1 個

1 元 2 個

第4題 檔名 5-4.py

寫一個函數,證明 n 個數的立方和等於 n 個數的和的平方。

執行結果參考:

請輸入一個大於0的正整數: 3

1~3 + 2~3 + 3~3 = 36

(1 + 2 + 3)~2 = 36

請輸入一個大於0的正整數: 10

1~3 + 2~3 + 3~3 + 4~3 + 5~3 + 6~3 + 7~3 + 8~3 + 9~3 + 10~3 = 3025

(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10)~2 = 3025

這裡我們先用 ~ 代表次方的符號。

提示: for 迴圈的 range() 函數可以接受兩個參數,第一個參數是迴圈的起始點,第二個參數是結束點加一。

>>> for i in range(3, 6):
...     print(i)
...
3
4
5
第5題 檔名 5-5.py

改寫上面的動動腦第2題,我們希望讓打招呼程式更靈活一點。

  1. 凌晨3點到中午十二點前說早安
  2. 中午十二點到晚上六點前說午安
  3. 晚上六點以後到凌晨三點說晚安
第6題 檔名 5-6.py

for 迴圈與 range() 練習,列出每小題符合條件的 for 迴圈並印出數值。

  1. 從 100 到 1 之間的所有奇數。
  2. 從 10 到 100 之間 5 的倍數。
  3. 從 3 到 10 之間的每個數字,規定 range() 內一定要用三個參數。
第7題 檔名 5-7.py

寫出符合下列所有條件的虛擬碼程式。

  1. 下雨就帶傘出門
  2. 晴天就戴太陽眼鏡出門
  3. 陰天就背包包出門
  4. 其他狀況就在家睡覺不出門

參考資料

三角形的三邊關係

內建函數與遞迴

日期與時間 time

日期與時間 datetime

影片

第五章 條件與遞迴 part one

第五章 條件與遞迴 part two

最後更新:2021-10-08 22:00:28

From: 111.249.165.250

By: 特種兵