學習軌道
/
Python
Python
/
練習
/
紙牌遊戲
紙牌遊戲

紙牌遊戲

學習練習

簡介

list是一種可變的集合,其中的項目按_順序_排列。就像大多數集合一樣(參見內建的 tuple、dict 和 set),陣列可以參照任何(或多種)資料型態,包括其他陣列。如同任何序列,可以透過從左邊算起的 0-based index 編號,或從右邊算起的 -1-based index 來存取項目。陣列可以透過切片表示法或<list>.copy()來整份或部分複製。

陣列同時支援一般和可變序列操作,例如min()/max()、<list>.index()、<list>.append()和<list>.reverse()。陣列元素可以使用for item in <list>建構來疊代。當同時需要元素索引和元素值時,可以使用for index, item in enumerate(<list>)。

在底層,lists是以動態陣列實作的,類似 Java 的ArrayList型別,最常用來儲存長度未知、性質相近的一組資料(字串、數字、集合等)。陣列是非常有彈性又實用的資料結構,Python 中許多內建方法和操作都會產生陣列作為輸出。

建構

list可以用_字面值_來宣告,兩側是方括號[],並以逗號分隔元素:

>>> no_elements = []

>>> no_elements
[]

>>> one_element = ["Guava"]

>>> one_element
['Guava']

>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]

>>> elements_separated_with_commas
['Parrot', 'Bird', 334782]

為了方便閱讀,當list中有許多元素或巢狀資料結構時,可以加上換行:

>>> lots_of_entries = [
      "Rose",
      "Sunflower",
      "Poppy",
      "Pansy",
      "Tulip",
      "Fuchsia",
      "Cyclamen",
      "Lavender"
   ]
   
>>> lots_of_entries
['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']

# Each data structure is on its own line to help clarify what they are.
>>> nested_data_structures = [
      {"fish": "gold", "monkey": "brown", "parrot": "grey"},
      ("fish", "mammal", "bird"),
      ['water', 'jungle', 'sky']
   ]
   
>>> nested_data_structures
[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]

list()建構子可以空著使用,也可以接受一個_可疊代物件_作為引數。建構子會依序走訪可疊代物件中的每個元素,並將它們加入list:

>>> no_elements = list()

>>> no_elements
[]

# The tuple is unpacked and each element is added.
>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))

>>> multiple_elements_from_tuple
['Parrot', 'Bird', 334782]

# The set is unpacked and each element is added.
>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})

>>> multiple_elements_from_set
[2, 3, 5, 7, 11]

用list建構子搭配string或dict時,結果可能會出乎意料:

# String elements (Unicode code points) are iterated through and added *individually*.
>>> multiple_elements_string = list("Timbuktu")

>>> multiple_elements_string
['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']

# Unicode separators and positioning code points are also added *individually*.
>>> multiple_code_points_string = list('अभ्यास')

>>> multiple_code_points_string
['अ', 'भ', '्', 'य', 'ा', 'स']

# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
>>> source_data = {"fish": "gold", "monkey": "brown"}

>>> multiple_elements_dict_1 = list(source_data)
['fish', 'monkey']

由於list建構子只接受_可疊代物件_(或什麼都不接受)作為引數,_不可_疊代的物件會擲回型別錯誤。因此,改用字面值的方式來建立只有一個項目的list,會簡單得多。

# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
>>> one_element = list(16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
>>> one_element_from_iterable = list((16,))

>>> one_element_from_iterable
[16]

存取元素

陣列中的項目(以及其他序列型別str和tuple中的項目)可以透過0-based index和_括號表示法_來存取。索引可以從**left到right(從零開始),也可以從right到left**(從 -1 開始)。

從左側算起的索引 ⟹






0
👇🏾
1
👇🏾
2
👇🏾
3
👇🏾
4
👇🏾
5
👇🏾
P y t h o n
👆🏾
-6
👆🏾
-5
👆🏾
-4
👆🏾
-3
👆🏾
-2
👆🏾
-1





⟸ 從右側算起的索引
>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]

# Oatmeal is at index 0 or index -4.
>>> breakfast_foods[0]
'Oatmeal'

>>> breakfast_foods[-4]
'Oatmeal'

# Eggs are at index -2 or 2
>>> breakfast_foods[-2]
'Eggs'

>>> breakfast_foods[2]
'Eggs'

# Toast is at -1
>>> breakfast_foods[-1]
'Toast'

list中某一段的元素可以透過_切片表示法_(<list>[start:stop])來存取。_切片_是指在位置index上、滿足start <= index < stop的一段元素序列。_切片_會回傳被「切出」項目的複本,不會修改原本的list。

也可以使用step參數[start:stop:step]來「跳過」或篩選list元素(例如step為 2 時,會選取範圍中每隔一個的元素):

>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]

# If there is no step parameter, the step is assumed to be 1.
>>> middle_colors = colors[2:6]

>>> middle_colors
['Green', 'Yellow', 'Orange', 'Pink']

# If the start or stop parameters are omitted, the slice will
# start at index zero, and will stop at the end of the list.
>>> primary_colors = colors[::3]

>>> primary_colors
['Red', 'Yellow', 'Blue']

操作陣列

對陣列使用內建的sum()函式,會回傳陣列中所有數字的總和:

>>> number_list = [1, 2, 3, 4]
>>> sum(number_list)
10

你也可以用len()函式取得陣列的_長度_:

>>> long_list = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
>>> len(long_list)
10

陣列也可以用各種方式合併:

# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]

>>> new_via_concatenate
['George', 5, 'cat', 'Tabby']

# Likewise, using the multiplication operator * is the equivalent of using + n times.
>>> first_group = ["cat", "dog", "elephant"]
>>> multiplied_group = first_group * 3

>>> multiplied_group
['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']

陣列提供_疊代器_,可以像其他_序列型別_一樣用迴圈走訪。

#  Looping through the list and printing out each element.
>>> colors = ["Orange", "Green", "Grey", "Blue"]

>>> for item in colors:
...     print(item)
...
Orange
Green
Grey
Blue

如果想更深入了解loops和iterators,就完成loops概念吧。

說明

伊莉絲非常期待在即將到來的拉斯維加斯之旅中玩幾局撲克(還有其他卡牌遊戲)。她是「自我追蹤」的忠實愛好者,想寫幾個小函式來幫忙處理追蹤事項,還請你一起幫她想想該怎麼做。

1. 追蹤撲克牌局

伊莉絲特別喜歡撲克,她想記錄自己玩了幾局,以及究竟是_哪幾局_。每一局都有自己的編號,每張牌桌也都會顯示目前正在進行的局數。伊莉絲選了一張牌桌坐下來玩她的第一局。她打算玩三局。

實作一個函式 get_rounds(<round_number>),它接受目前的局數,並回傳一個 list,內容包含該局以及接下來_即將到來的兩局_:

>>> get_rounds(27)
[27, 28, 29]

2. 把所有牌局放在同一個地方

伊莉絲在第一張牌桌玩了幾局,接著休息一下,又到第二張牌桌玩了幾局……但最後每張牌桌都各拿到一份不同的陣列!她想把這兩份陣列合在一起,這樣就能在同一處追蹤所有的撲克牌局。

實作一個函式 concatenate_rounds(<rounds_1>, <rounds_2>),它接受兩個陣列,回傳一個 list,內容是第一個 list 中的所有牌局,接著是第二個 list 中的所有牌局:

>>> concatenate_rounds([27, 28, 29], [35, 36])
[27, 28, 29, 35, 36]

3. 找出先前的牌局

另一個玩家在聊到之前某幾局撲克時,提到其中兩局打起來非常相似。伊莉絲不確定自己到底有沒有玩過那幾局。

實作一個函式 list_contains_round(<rounds>, <round_number>),它接受兩個引數:一個是玩過的牌局陣列,另一個是局數。如果該局在玩過的牌局陣列裡,函式會回傳 True;否則回傳 False:

>>> list_contains_round([27, 28, 29, 35, 36], 29)
True

>>> list_contains_round([27, 28, 29, 35, 36], 30)
False

4. 計算牌面點數的平均值

伊莉絲想試試一款叫做 Black Joe 的新遊戲。它跟 Black Jack 很像,目標是讓手上的牌加總成某個目標點數,但在 Black Joe 裡,目標是讓牌面點數的_平均值_變成 7。求平均值的方法是把所有牌面點數加起來,再除以手上牌的張數。

實作一個函式 card_average(<hand>),回傳 Black Joe 中一手牌的平均點數。

>>> card_average([5, 6, 7])
6.0

5. 其他求平均值的方法

在 Black Joe 裡,速度很重要。伊莉絲想試著找出更快求出平均值的方法。

她想到兩種能得到_類平均值_數字的方法:

  • 取手中_第一張_和_最後一張_牌的點數平均值。
  • 使用這手牌的中位數(中間那張牌)。

實作函式 approx_average_is_average(<hand>),其中 hand 是一個陣列,包含你手上每張牌的點數。

只要上述其中_一種_or兩種_策略算出的數字_等於 實際平均值,就回傳 True。

注意:所有手牌的張數都是奇數,這樣比較容易找出中位數。

>>> approx_average_is_average([1, 2, 3])
True

>>> approx_average_is_average([2, 3, 4, 8, 8])
True

>>> approx_average_is_average([1, 2, 3, 5, 9])
False

6. 更多求平均值的技巧

對平均值實驗的結果感到好奇,伊莉絲想知道取_偶數_位置牌的平均值,跟取_奇數_位置牌的平均值,會不會得到相同的結果。該是再寫一個測試函式的時候了!

實作一個函式 average_even_is_average_odd(<hand>),回傳一個布林值,表示偶數索引位置牌的平均值是否等於奇數索引位置牌的平均值。

>>> average_even_is_average_odd([1, 2, 3])
True

>>> average_even_is_average_odd([1, 2, 3, 4])
False

7. 獎勵局規則

Black Joe 中每第 11 手牌都是獎勵手牌,附帶一條獎勵規則:如果你抽到的最後一張牌是傑克,就把它的點數加倍。

實作一個函式 maybe_double_last(<hand>),它接受一手牌,並檢查最後一張牌是不是傑克(11)。如果最後一張牌是傑克(11),就在回傳這手牌之前把它的點數加倍。

>>> hand = [5, 9, 11]
>>> maybe_double_last(hand)
[5, 9, 22]

>>> hand = [5, 9, 10]
>>> maybe_double_last(hand)
[5, 9, 10]
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Python Exercism

準備好開始 紙牌遊戲 了嗎?

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