「你有幫我問候她嗎?」
「當然。」
最好是有,那個混蛋。
「那她怎麼說呢?」我問:「她依然堅持玩東南西北的時候從東出發嗎?」
「沒啦,我才沒問她。你以為我們整晚耗在那是要做什麼?玩東南西北?」~節錄自《賴田捕手》第六章
今天繼續來介紹 Python 提供的兩種常用物件。
Python 提供的另一個有趣的物件是字典(dict)類型的物件。這種物件,是由鑰匙(key):內容(value)這樣一組一組的對應關係所組成。在一個字典物件中,鑰匙是獨一無二的,其所對應的內容,可以是任何類型的物件,可以是布林物件(boolean)、整數(integer)、浮點數(float)、字串(string)、清單(list)、不可變更清單(tuple)、甚至是現在介紹的字典(dict)。
不知道大家小時候有沒有玩過一種叫做「東南西北」➀ 的摺紙遊戲?遊戲的玩法很簡單,首先摺出一個像鉗嘴一樣分成四瓣的小玩意,如圖一。接著分別在每一瓣上分別寫上一個方位,習慣上是「東-南-西-北」順時針寫上去。這時候遊戲的玩家就會先喊出一個起始的方位跟移動的步數,決定好以後,就拿起東南西北這個小玩意,從起始的方位開始,讓鉗嘴一開一合,同時唸著「東、南、西、北、東、南、西、北…」,看看最後會移動到哪個方位,同時按著寫在鉗嘴裡,代表該方位的指令做動作。有的時候可能是「連續波比跳十下」或是「原地後空翻一圈」,不過有的時候也有可能會是「寫出費馬最後定理的證明」。
圖一、東南西北示意圖 ➀
信不信由你,草泥馬們喜愛這個遊戲簡直到了癡狂的地步。當你面對一群暴躁不安,即將失控的草泥馬團體,切莫驚慌失措,也不須拔腿就跑,只要花個一分鐘從口袋掏出一張色紙,細細的摺好一個東南西北,把它拿到草泥馬面前晃一晃,然後平心靜氣地告訴牠們,如果再鬧,就不要想玩東南西北。牠們就會馬上乖乖站好,安靜下來聽你說話了。
現在我們就來用 Python 裡提供的字典物件,來創造一個東南西北。
要怎麼創造字典物件呢?很簡單,只要把鑰匙:內容組合放進{}
當中就行,不同的鑰匙:內容組合之間,用,
互相隔開。而要放入多少組鑰匙:內容組合,就看你的需求。
In [1]: ESWN_dict = {'東': '前滾翻', '南': '後滾翻', '西': '前空翻', '北': '後空翻'}
type(ESWN_dict)
Out[1]: dict
這樣我們就做出第一個字典物件了。在這邊,我們選用字串來當作字典物件的鑰匙。事實上,還可以選用包括布林物件(boolean)、整數物件(integer)、浮點數物件(float)、字串物件(string)、不可變更清單(tuple)等等的當作字典的鑰匙,只要他們在字典中是獨一無二就行。一個字典物件中,不會有兩個相同的鑰匙。對於內容的規定,則相當簡單:任何你想到的物件,都可以當作字典的內容。不同的鑰匙要選用相同的內容,Python 也不會反對。
In [2]: temp_dict = {'亂': [1, 2, 3],
7: (4, 5, 6),
8: 8,
'糟': 'bad',
'no': '重複的',
('字典'): '重複的'}
temp_dict
Out[2]: {'亂': [1, 2, 3], 7: (4, 5, 6), 8: 8, '糟': 'bad', 'no': '重複的', '字典': '重複的'}
另外,也可以用dict()
,從一些看起來像字典的物件中,創造出字典物件。什麼叫「看起來像字典的物件」呢?不囉嗦,看程式碼:
In [3]: dict([[1, 2],
[3, 4],
[5, 6]])
Out[3]: {1: 2, 3: 4, 5: 6}
甚至還可以:
In [4]:dict(['a1',
'b2',
'c3',
'd4'])
Out[4]: {'a': '1', 'b': '2', 'c': '3', 'd': '4'}
有了東南西北,遊戲就可以開始了。草泥馬說:「南,十步」。我開始算:「西北東南西北東南西北」,原來是北,走到「北」要做什麼呢?讓我們建立的字典來說說:
In [5]: ESWN_dict['北']
Out[5]: '後空翻'
跟清單不同,字典物件中的項目,沒有位置這種概念,因此不能用ESWN_dict[2]
這樣的方法來操作字典。相反的,字典有鑰匙,我們要選擇字典中的項目,就要用我們給的鑰匙。我們可以用keys()
、values()
、items()
,分別查看字典中的鑰匙、內容、跟鑰匙:內容組合。
In [6]: print('keys\t:', ESWN_dict.keys())
print('values\t:', ESWN_dict.values())
print('items\t:', ESWN_dict.items())
keys : dict_keys(['東', '南', '西', '北'])
values : dict_values(['前滾翻', '後滾翻', '前空翻', '後空翻'])
items : dict_items([('東', '前滾翻'), ('南', '後滾翻'), ('西', '前空翻'), ('北', '後空翻')])
注意到,我們使用了keys()
等等的指令,Python 回傳給了我們一個可以數(ˇ)數(ˋ)的清單,但又有點不像清單?事實上,這個確實不是清單,你喜歡的話,可以叫它字典清單?清單能夠做到的事,字典清單多半做不到。
In [7]: ESWN_dict.keys()[0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-19-17f4e9361812> in <module>
----> 1 ESWN_dict.keys()[0]
TypeError: 'dict_keys' object is not subscriptable
就更別提append()
等等的指令了。
字典清單少數能做的事,就是提供訊息,以及可以數(ˇ)數(ˋ)(iterable)這個特性。
In [8]: iter(ESWN_dict.keys())
Out[8]: <dict_keyiterator at 0x27bc7b9eae8>
可以數(ˇ)數(ˋ)有什麼好說嘴的呢?我們等等再告訴你。
第一隻草泥馬拿到指令之後,就開心地去表演牠的後空翻了。第二隻草泥馬上來:「西,87」。原來是西87呀,調皮的草泥馬。以為我數不了87步是吧?告訴你吧,我確實是不太想數。怎麼辦呢?既然我們開始學 Python 了,就請 Python 來幫我們數吧!
In [9]: start = '西'
steps = 87
offset = steps % len(ESWN_dict.keys())
ESWN_list = list(ESWN_dict.keys())
final = ESWN_list[(ESWN_list.index(start)+offset) % len(ESWN_dict.keys())]
ESWN_dict[final]
Out[9]: '後滾翻'
如果我沒有搞錯的話,上面全都是我們學過的東西。一時之間看不懂沒關係,要看懂別人胡亂寫的程式碼本來就需要花點工夫。我一行一行慢慢解釋:
start = '西'
start
來標示開始的位置。 steps = 87
step
來標示我們要走的步數。 offset = steps % len(ESWN_dict.keys())
len(ESWN_dict.keys())
讓 Python 自己找出究竟是幾步一個循環。Python 可聰明的呢!(當然,聰明的你,想改成用 len(ESWN_dict.values())
或是len(ESWN_dict.items())
,甚至是len(ESWN_dict)
都可以!) ESWN_list = list(ESWN_dict.keys())
['東', '南', '西', '北']
這樣的清單,而字典清單又不是真正的清單,所以我先用list()
把字典清單變成真正的清單。 final = ESWN_list[(ESWN_list.index(start)+offset) % len(ESWN_dict.keys())]
ESWN_list[0]
是'東'
,ESWN_list[1]
是'南'
,ESWN_list[2]
是'西'
,ESWN_list[3]
是'北'
,要跟 Python 溝通,就要懂 Python 的心。所以我用index()
先去尋找'西'
是在清單中的哪個位置。接著從這個位置往後走 3 步。要小心,往後走 3 步之後,很可能會走到下一個循環,因此我再用%
的運算找一次餘數。當然,聰明的你,看到這裡或許會發現,哈哈,第二行根本就是多餘的嘛,其實可以把第二行刪掉,而這一行改成: final = ESWN_list[(ESWN_list.index(start)+stpes) % len(ESWN_dict.keys())]
ESWN_dict[final]
下面我要偷渡一個新的物件,叫做函數(function)。想想看,我們辛辛苦苦打了 6 行程式碼(被大家抓包之後剩 5 行),跑了一次之後就結束了,太可惜了吧?不是我在說嘴,接下來可還有 9527 隻草泥馬要等著玩東南西北呢!
In [10]: def ESWN_function(start, steps):
ESWN_list = list(ESWN_dict.keys())
final = ESWN_list[(ESWN_list.index(start)+steps) % len(ESWN_dict.keys())]
return ESWN_dict[final]
type(ESWN_function)
Out[10]: function
上面那幾行程式碼在說什麼呢?首先,我用def
定義了一個名稱為ESWN_function
的函數,start
跟steps
是這個函數要接受的參數。把參數丟進函數裡,經過一些操作,最後函數會回傳(return
)給我們一個物件。上面那個例子裡,則是ESWN_dict[final]
。
怎麼運用這個物件呢?也不困難:
In [11]: ESWN_function('西', 87)
Out[11]: '後滾翻'
函數就先講到這裡,之後我還會再花一點篇幅來討論它。繼續回頭看我們的字典物件。
既然都已經有'東'、'南'、'西'、'北'了,怎麼可以沒有'中'呢?來試著在現有的字典裡加入新的鑰匙:內容組合吧。最簡單的方式就是直述句,有話直說:
In [12]: ESWN_dict['中'] = '神通'
這樣 Python 會接受嗎?
In [13]: ESWN_dict
Out[13]: {'東': '前滾翻', '南': '後滾翻', '西': '前空翻', '北': '後空翻', '中': '神通'}
咦!它接受耶!
有'東'、'南'、'西'、'北'了,那怎麼可以沒有'東南'、'西南'、'東北'、'西北'呢?
In [14]: more_direction = {'東南':'啊?', '西南':'什麼?', '東北':'什麼啊?', '西北':'什麼啊啊啊?'}
ESWN_dict.update(more_direction)
ESWN_dict
Out[14]: {'東': '前滾翻',
'南': '後滾翻',
'西': '前空翻',
'北': '後空翻',
'中': '神通',
'東南': '啊?',
'西南': '什麼?',
'東北': '什麼啊?',
'西北': '什麼啊啊啊?'}
我們可以用update()
,將more_direction
這個字典物件中的鑰匙:內容組合加入到ESWN_dict
這個字典物件裡面。但是等等,東南西北這個遊戲裡面,根本就沒有這些東西啊。
In [15]: del ESWN_dict['中']
del ESWN_dict['東南']
del ESWN_dict['西南']
del ESWN_dict['東北']
del ESWN_dict['西北']
ESWN_dict
Out[15]: {'東': '前滾翻', '南': '後滾翻', '西': '前空翻', '北': '後空翻'}
做蠢事了我。
Python 另外提供了一種有趣的物件,叫做集合。集合跟清單有一點像,不過集合中的項目是獨一無二的,不會重複。因此,也有人會把集合看作「沒有內容只有鑰匙的字典」。怎麼說呢?我們先試著比較用list()
跟set()
創造清單物件跟集合物件看看:
In [16]: temp_str = 'OOOOOOOOPS!!!!'
print('List\t:', list(temp_str))
print('Set\t:', set(temp_str))
List : ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'P', 'S', '!', '!', '!', '!']
Set : {'O', 'S', 'P', '!'}
集合裡的項目是獨一無二的。因此集合更像是字典,而我們也可以用{}
類似創造字典的方式來創造集合:
In [17]: temp_set = {'O', 'O', 'P', 'S', '!', '!'}
temp_set
Out[17]: {'!', 'O', 'P', 'S'}
集合就像是沒有內容的字典。所以同樣的,集合裡面沒有位置的概念,我們也沒辦法透過位置來呼叫集合裡的項目。事實上,我們需要呼叫集合裡的項目嗎?我們只需要知道某個項目是否在集合當中就夠用了。
In [18]: 'O' in temp_set
Out[18]: True
當你和草泥馬玩東南西北玩得夠久的時候,你就會看出來,其實草泥馬們各有所長。有些草泥馬擅長前滾翻,有些草泥馬能翻出漂亮的後滾翻,而有些草泥馬在前空翻這個領域可以說是才華洋溢。我試著用集合來記錄在前空翻跟後空翻這兩個領域表現特別突出,可謂出神入化的幾隻草泥馬。
In [19]: 前空翻大師 = {'法蘭克', '藍能', '吉姆', '約翰'}
後空翻大師 = {'威廉', '吉姆', '彼得', '大衛', '藍能'}
看得出來總共有幾隻草泥馬能表演出精湛的前空翻嗎?
In [20]: len(前空翻大師)
Out[20]: 4
太簡單了。難一點的:看得出來有幾隻草泥馬同時擅長前空翻跟後空翻嗎?
In [21]: 前空翻大師 & 後空翻大師
Out[21]: {'吉姆', '藍能'}
那麼,擅長前空翻卻不是那麼擅長後空翻的呢?
In [22]: 前空翻大師 - 後空翻大師
Out[22]: {'法蘭克', '約翰'}
只擅長其中一種空翻的草泥馬?
In [23]: 前空翻大師 ^ 後空翻大師
Out[23]: {'大衛', '威廉', '彼得', '法蘭克', '約翰'}
在空翻領域,不論前空翻或後空翻,達到大師級別的草泥馬有誰呢?
In [24]: 前空翻大師 | 後空翻大師
Out[24]: {'吉姆', '大衛', '威廉', '彼得', '法蘭克', '約翰', '藍能'}
啊,等等,我忘了'藍道'。那隻不服輸的草泥馬,每天鍛鍊自己,終於在前一陣子把後空翻練的臻於化境了。為了回應牠的努力,我也得'藍道'加入後空翻大師的名單裡才行呢!
In [25]: 後空翻大師 |= {'藍道'}
後空翻大師
Out[25]: {'吉姆', '大衛', '威廉', '彼得', '藍能', '藍道'}
如果你想知道的話,事實上,用在集合的這幾個運算子&
、|
、-
、^
有另外的表示方法,我把它們整理在表一。
表一、集合(set)的運算子或運算方法 ➂
運算 | 運算子 | 或是你也可以這樣 | 範例 |
---|---|---|---|
交集 | & |
intersection() |
前空翻大師.intersection(後空翻大師) |
聯集 | ` | ` | union() |
差集 | - |
difference() |
前空翻大師.difference(後空翻大師) |
對稱差 | ^ |
symmetric_difference() |
前空翻大師.symmetric_difference(後空翻大師) |
謝謝大家今天又來看我胡言亂語。我把今天用到的程式碼範例都放在 Github 上了(Github 或 nbviewer),有興趣的或覺得我講得不夠清楚的,可以把程式碼下載下來玩玩!或是參考網路上強者大大提供的 Introducing Python 讀書筆記。➃
##參考資料
➀ 東南西北 wiki
➁ 費馬最後定理 wiki
➂ 集合 wiki
➃ Introducing Python 讀書筆記第三章
註:對於此系列文有興趣的讀者,歡迎參考由此系列文擴編成書的 LINE Bot by Python,以及最新的系列文《賴田捕手:追加篇》
第 31 天 初始化 LINE BOT on Heroku
第 32 天 快速回覆 QuickReply 介紹
第 33 天 妥善運用 Heroku APP 暫存空間
第 34 天 妥善運用 LINE Notify 免費推播
第 35 天 製造 Deploy to Heroku 按鈕
非常謝謝版主與草泥馬們的教學~因為是初次學習XD,卡在"西,87"的遊戲中有點久,哈哈。
對程式碼不太熟悉的我,用了土法煉鋼的方法..
ESWN_dict = {'東': '前滾翻', '南': '後滾翻', '西': '前空翻', '北': '後空翻'}
#請問「西,走87步」是什麼動作呢?
start = '西'
steps = 87
keys = len(ESWN_dict.keys()) #4
ans = steps % keys #3
ESWN_list = list(ESWN_dict.keys()) #list
total = ESWN_list[(ESWN_list.index(start)+ans) %keys] #這段有點不太理解
ESWN_dict[total]
終於搞定了xD ,之中用了很多print。
不過,對於total那一行的..請問是不是因為超過原有的list數量,超過範圍才再除keys 求餘數呢?
您好
沒錯,就是你理解的那樣!ESWN_list
當中的物件只有 4 個
In [1]: print(ESWN_list)
Out[1]: ['東', '南', '西', '北']
而:
total = ESWN_list[(ESWN_list.index(start)+ans) % keys]
這一行當中,我要利用清單當中,每個物件的編號來呼叫該物件:
In [2]: ESWN_list[0]
Out[2]: '東'
這時候要注意一件事,查詢的編號不能大於等於清單的長度(因為 Python 是從 0 開始編號)
In [3]: ESWN_list[4]
Out[3]:
IndexError: list index out of range
如果不做% keys
的動作,就可能發生這個錯誤。
另外,寫到這邊,就會發現
ans = steps % keys
這一行其實是多餘的,因為我們無論如何都要利用
total = ESWN_list[(ESWN_list.index(start)+ans) % keys]
再做一次% keys
的操作。
希望這麼說明能更好理解一些!