21 點

21 點

學習練習

簡介

比較

Python 支援以下基本的比較運算子:

運算子 運算 說明
> 「大於」 若 a 的值嚴格大於 b,則 a > b 為 True
< 「小於」 若 a 的值嚴格小於 b,則 a < b 為 True
== 「等於」 若 a 的值嚴格等於 b,則 a == b 為 True
>= 「大於或等於」 若 a > b 或 a == b,則 a >= b 為 True
<= 「小於或等於」 若 a < b 或 a == b,則 a <= b 為 True
!= 「不等於」 若 a == b 為 False,則 a != b 為 True
is 「同一性」 若 a 和 b 是同一_物件_,則 a is b 為 True(且僅限於此情況)
is not 「否定的同一性」 若 a 和 b 不是同一_物件_,則 a is not b 為 True
in 「包含測試」 若 a 是 b 的成員、子集合或元素,則 a in b 為 True
not in 「否定的包含測試」 若 a 不是 b 的成員、子集合或元素,則 a not in b 為 True

它們的優先順序都相同(高於 布林運算,但低於算術或位元運算)。

不同資料型態之間的比較

不同型態的物件(數值型態除外)預設永遠不會比較為相等。 class 的非同一實例也 不會 比較為相等,除非該 class 定義了特殊的 豐富比較 方法,來自訂 object 預設的比較行為。 透過 rich comparisons 進行自訂會在某個後續的練習中介紹。 關於這個主題的(更多)細節,請參閱 Python 文件中的 值比較。

數值型態(大多)是這個型態配對規則的例外。 只要型態可以隱含轉換以供比較,integer 可以被視為等於 float(或 octal 等於 hexadecimal)。

對於 Python 標準函式庫中的其他數值型態(複數、十進位數、分數),比較運算子只在「合理」的地方才有定義(隱含轉換不會改變結果的地方),但如果底層物件無法準確轉換以供比較,就會擲出 TypeError。 關於 Python 用於_數值轉換_的規則的詳細資訊,請參閱 Python 文件中的 算術轉換。

>>> import fractions

# A string cannot be converted to an int.
>>> 17 == '17'
False

# An int can be converted to float for comparison.
>>> 17 == 17.0
True

# The fraction 6/3 can be converted to the int 2
# The int 2 can be converted to 0b10 in binary.
>>> 6/3 == 0b10
True

# An int can be converted to a complex number with a 0 imaginary part.
>>> 17 == complex(17)
True

# The fraction 2/5 can be converted to the float 0.4
>>> 0.4 == 2/5
True

>>> complex(2/5, 1/2) == complex(0.4, 0.5)
True

任何數字與 NaN(不是數字)型態進行的有序比較結果都是 False。 Python NaN 定義的一個令人困惑的副作用是:NaN 永遠不會等於 NaN。

>>> x = float('NaN')

>>> 3 < x
False

>>> x < 3
False

# NaN never compares equal to NaN
>>> x == x
False

比較字串

與數字不同,字串(str)是使用各自的 Unicode 碼點依字典序進行比較(也就是將 str 中的每個碼點傳入內建函式 ord() 所得到的結果,它會回傳一個 int)。 如果兩個字串中的所有碼點都相符,且 順序相同,這兩個字串就被視為相等。 這種比較是以「成對」的方式進行:第一個對第一個、第二個對第二個,依此類推。 在 Python 3.x 中,str 和 bytes 無法直接強制轉換或比較。

>>> 'Python' > 'Rust'
False

>>> 'Python' > 'JavaScript'
True

# Examples with Mandarin.
# hello < goodbye
>>> '你好' < '再见'
True

# ord() of first characters
>>> ord('你'), ord('再')
(20320, 20877)

# ord() of second characters
>>> ord('好'), ord('见')
(22909, 35265)

# And with Korean words.
# Pretty < beautiful.
>>> '예쁜' < '아름다운'
False

>>> ord('예'), ord('아')
(50696, 50500)

比較串接

比較運算子可以_任意_串接,意思是它們能以任何長度的任何組合來使用。 請注意,運算式的求值是由 left 到 right 進行。

舉例來說,x < y <= z 等於 x < y and y <= z,差別在於 y 只會被求值一次。 在兩種情況下,當發現 x < y 為 False 時,z 完全_不會_被求值。 這通常稱為 short-circuit evaluation:如果運算式的真值已經確定,求值就會停止。

Short circuiting 受到各種布林運算子、函式的支援,Python 中的比較串接也支援它。 與許多其他程式語言(包括 C、C++、C# 和 Java)不同,Python 中像 a < b < c 這樣的串接運算式具有傳統的數學詮釋與優先順序。

>>> x = 2
>>> y = 5
>>> z = 10

>>> x < y < z
True

>>> x < y > z
False

>>> x > y < z
False

比較物件同一性

運算子 is 和 is not 測試的是物件的同一性,而非物件的_值_。 物件建立之後,其同一性永遠不會改變,可以透過 id() 函式取得。

若 且僅限於 id(<apple>) == id(<orange>),則 <apple> is <orange> 的求值結果為 True。 <apple> is not <orange> 則會得到相反的結果。

由於 None 和 NotImplemented 具有單例的地位,應該一律使用 is 和 is not 來與項目進行比較。 關於這個慣例的更多細節,請參閱 Python 參考文件中的值比較和 PEP8。

>>> my_fav_numbers = [1, 2, 3]

>>> your_fav_numbers = my_fav_numbers

>>> my_fav_numbers is your_fav_numbers
True

# The returned id will differ by system and python version.
>>> id(my_fav_numbers)
4517478208

# your_fav_numbers is only an alias pointing to the original my_fav_numbers object.
# Assigning a new name does not create a new object.
>>> id(your_fav_numbers)
4517478208


>>> my_fav_numbers is not your_fav_numbers
False

>>> my_fav_numbers is not None
True

>>> my_fav_numbers is NotImplemented
False

成員資格比較

運算子 in 和 not in 測試的是_成員資格_。 若 <fish> 是 <soup> 的成員(<fish> 是 <soup> 的子集合,或包含於 <soup> 之中),則 <fish> in <soup> 的求值結果為 True,否則為 False。 <fish> not in <soup> 會回傳其否定,也就是 <fish> in <soup> 的_相反結果_。

對於字串和位元組型態,若 且僅限於 <name> 是 <fullname> 的子字串,則 <name> in <fullname> 為 True。

# A set of lucky numbers.
>>> lucky_numbers = {11, 22, 33}
>>> 22 in lucky_numbers
True

>>> 44 in lucky_numbers
False

# A dictionary of employee information.
>>> employee = {'name': 'John Doe', 
                'id': 67826, 'age': 33, 
                'title': 'ceo'}

# Checking for the membership of certain keys.
>>> 'age' in employee
True

>>> 33 in employee
False

>>> 'lastname' not in employee
True

# Checking for substring membership
>>> name = 'Super Batman'
>>> 'Bat' in name
True

>>> 'Batwoman' in name
False

說明

在這個練習中,你將會實作二十一點的某些規則,例如遊戲的進行方式與計分方式。

注意:在這個練習中,_A代表么點,J代表傑克,Q代表皇后,而K_代表國王。 鬼牌會被移除。 這裡假設使用的是一副標準的法式 52 張撲克牌,但在大多數玩法中,會將數副牌一起洗勻後再玩。

1. 計算一張牌的值

在二十一點中,么點究竟值 1 分還是 11 分,由每位玩家自行決定(之後會再詳細說明)。 人頭牌(J、Q、K)計為 10 分,其餘的牌則依其「點數」(數值)計分。

定義value_of_card(<card>)函式,參數為card。 這個函式應該回傳傳入的牌面字串所代表的_數值_。 由於么點可以有多個值(1 或 11),這個函式目前會先把么點牌的值固定為 1。 稍後你將會實作一個函式,在已知現有手牌的情況下判斷么點牌的值。

>>> value_of_card('K')
10

>>> value_of_card('4')
4

>>> value_of_card('A')
1

2. 判斷哪張牌的值較高

定義higher_card(<card_one>, <card_two>)函式,參數為card_one和card_two。 就計分而言,J、Q或K的值為 10。 這個函式應該回傳哪張牌在計分上具有較高的值。 如果兩張牌的值相同,就回傳兩張。 只要在return敘述中使用逗號,就能回傳兩張牌:

# Using a comma in a return creates a Tuple.  Tuples will be covered in a later exercise.
>>> def returning_two_values(value_one, value_two):
        return value_one, value_two

>>> returning_two_values('K', '3')
('K', '3')

么點可以有多個值,所以在這個任務中,我們會把A牌的值固定為 1。

>>> higher_card('K', '10')
('K', '10')

>>> higher_card('4', '6')
'6'

>>> higher_card('K', 'A')
'K'

3. 計算么點的值

正如前面提到的,么點的值可能是 1 或 11 分。 玩家會盡可能讓分數接近 21,但不要_超過_ 21(也就是「爆牌」)。

定義value_of_ace(<card_one>, <card_two>)函式,參數為card_one和card_two,代表在拿到么點牌_之前_,手中已有的兩張牌。 你的函式必須判斷接下來這張么點牌的值會是 1 還是 11,並回傳該值。 記住:加上么點後的這手牌,其值要盡可能地高,但_不能_超過 21。

提示:如果手牌中已經有么點,那麼接下來這張么點的值就是 1。

>>> value_of_ace('6', 'K')
1

>>> value_of_ace('7', '3')
11

4. 判斷是否為「天生二十一」或「二十一點」手牌

如果玩家拿到的前兩張牌是一張么點(A)和一張十點的牌(10、K、Q或J),那麼這名玩家的分數就是 21。 這就稱為二十一點手牌。

定義is_blackjack(<card_one>, <card_two>)函式,參數為card_one和card_two,代表一對牌。 判斷這兩張牌是否構成二十一點,如果是就回傳布林值True,否則回傳False。

注意:分數的_計算_可以用很多種方式完成。 但如果可以的話,我們希望你檢查手牌中(或某個特定位置)**是否**有么點與一張十點的牌,而不是把整手牌的值_加總_起來。

>>> is_blackjack('A', 'K')
True

>>> is_blackjack('10', '9')
False

5. 拆分對子

如果一手牌的前兩張牌值相同(例如兩張 6,或一張Q和一張K),玩家可以選擇把它們當成兩手獨立的牌來處理。 這就稱為「拆分對子」。

定義can_split_pairs(<card_one>, <card_two>)函式,參數為card_one和card_two,代表一對牌。 判斷這手兩張牌是否可以拆分成兩手。 如果這手牌可以拆分,就回傳布林值True,否則回傳False。

>>> can_split_pairs('Q', 'K')
True

>>> can_split_pairs('10', 'A')
False

6. 加倍下注

當一開始拿到的兩張牌加起來為 9、10 或 11 點時,玩家可以再下一筆與原本賭注相同的賭注。 這就稱為「加倍下注」。

定義can_double_down(<card_one>, <card_two>)函式,參數為card_one和card_two,代表一對牌。 判斷這手兩張牌是否可以「加倍下注」,可以就回傳布林值True,否則回傳False。

>>> can_double_down('A', '9')
True

>>> can_double_down('10', '2')
False
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Python Exercism

準備好開始 21 點 了嗎?

註冊 Exercism,透過 17 個概念146 個練習 和真人引導來學習並精通 Python,全部免費。