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

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

特種兵

特種兵圖像(預設)

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 (布林資料形態),不是字串,同時他們的首字母必須是大寫。

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

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

  • 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
>>> a == b
True

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 x > 0:
	print('x 是正數')

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

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

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

通常用於架構程式,類似虛擬碼的作用。

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

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

5-5 替代執行

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

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

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

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

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

5-6 帶狀條件

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

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':
	draw_a()
elif choice == 'b':
	draw_b()
elif choice == 'c':
	draw_c()

當第一組 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 的值並且以 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 print_n(s, n):
	if n <= 0:
		return
	print(s)
	print_n(s, n - 1)

當 n 小於或等於 0 時使用 return 結束函數並返回 __main__

否則就印出變數 s 裡面的值,並再次以 n - 1 為參數呼叫自己這個函數,流程與上個例子類似。

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

5-9 無限遞迴

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

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

def recurse():
	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()
type some thing
>>> text
type some thing

接受使用者輸入前在畫面上提示使用者應當輸入什麼資料是個好做法,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

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

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

除錯

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

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

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

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

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

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

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

動動腦

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('午安')

練習

第1題 檔名 5-1.py

撰寫一個名為 do_n 的函數,接受兩個參數,分別是 n 與 f。

函數的功能是調用 n 次 f,而 f 是一個函數物件。

第2題 檔名 5-2.py

如果三角形的其中一邊長度大於另兩邊長度的和就無法組成三角形,相等的話就可以組成等腰三角形。

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

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

第3題 檔名 5-3.py

寫一個可以找錢的函數。

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

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

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

執行結果範例:

請輸入多少錢: 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. 晚上六點以後到凌晨三點前說晚安

參考資料

內建函數與遞迴

日期與時間 time

日期與時間 datetime

最後更新:2020-10-18 21:58:58

From: 1.161.136.25

By: 特種兵