iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 5
0
Modern Web

從LINE BOT到資料視覺化:賴田捕手系列 第 5

第 05 天:Python:資料結構與基本操作(二)

第 05 天:Python:資料結構與基本操作(二)

「你有幫我問候她嗎?」
「當然。」
最好是有,那個混蛋。
「那她怎麼說呢?」我問:「她依然堅持玩東南西北的時候從東出發嗎?」
「沒啦,我才沒問她。你以為我們整晚耗在那是要做什麼?玩東南西北?」

~節錄自《賴田捕手》第六章

今天繼續來介紹 Python 提供的兩種常用物件。

字典(dict)及其基本操作

  Python 提供的另一個有趣的物件是字典(dict)類型的物件。這種物件,是由鑰匙(key):內容(value)這樣一組一組的對應關係所組成。在一個字典物件中,鑰匙是獨一無二的,其所對應的內容,可以是任何類型的物件,可以是布林物件(boolean)、整數(integer)、浮點數(float)、字串(string)、清單(list)、不可變更清單(tuple)、甚至是現在介紹的字典(dict)。
  不知道大家小時候有沒有玩過一種叫做「東南西北」➀ 的摺紙遊戲?遊戲的玩法很簡單,首先摺出一個像鉗嘴一樣分成四瓣的小玩意,如圖一。接著分別在每一瓣上分別寫上一個方位,習慣上是「東-南-西-北」順時針寫上去。這時候遊戲的玩家就會先喊出一個起始的方位移動的步數,決定好以後,就拿起東南西北這個小玩意,從起始的方位開始,讓鉗嘴一開一合,同時唸著「東、南、西、北、東、南、西、北…」,看看最後會移動到哪個方位,同時按著寫在鉗嘴裡,代表該方位的指令做動作。有的時候可能是「連續波比跳十下」或是「原地後空翻一圈」,不過有的時候也有可能會是「寫出費馬最後定理的證明」。

https://ithelp.ithome.com.tw/upload/images/20190913/20120178nRzw9kW0hj.png
圖一、東南西北示意圖 ➀

  信不信由你,草泥馬們喜愛這個遊戲簡直到了癡狂的地步。當你面對一群暴躁不安,即將失控的草泥馬團體,切莫驚慌失措,也不須拔腿就跑,只要花個一分鐘從口袋掏出一張色紙,細細的摺好一個東南西北,把它拿到草泥馬面前晃一晃,然後平心靜氣地告訴牠們,如果再鬧,就不要想玩東南西北。牠們就會馬上乖乖站好,安靜下來聽你說話了。
  現在我們就來用 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())
      草泥馬要我笨笨的數 87 步,我就數給牠看嗎?當然不是。玩這個遊戲,因為是「東南西北東南西北…」這樣一直往下數的,每走 4 步就又重新開始一個循環,所以走 4 步就跟走 8 步相同,也跟走 12 步相同,也跟走 16 步相同,也跟走 20 步相同,還要我繼續下去嗎?I can do this all day, if you want to know the truth.
      要怎麼告訴程式碼「東南西北」的這個遊戲特色呢?這邊我用餘數這個運算方式。因為 4 步一循環(O),4 除以 4 的餘數,跟 8 除以 4 的餘數,跟 12 除以 4 的餘數,通通都相同。這邊我刻意用len(ESWN_dict.keys())讓 Python 自己找出究竟是幾步一個循環。Python 可聰明的呢!(當然,聰明的你,想改成用 len(ESWN_dict.values())或是len(ESWN_dict.items()),甚至是len(ESWN_dict)都可以!)
      因此我知道,走 87 步最後到達的位置,其實就跟走 3 步一樣的。想叫我走 87 步?想得美啊草泥馬。
  • 第四行: ESWN_list = list(ESWN_dict.keys())
      因為我等等會用到['東', '南', '西', '北']這樣的清單,而字典清單又不是真正的清單,所以我先用list()把字典清單變成真正的清單。
  • 第五行: final = ESWN_list[(ESWN_list.index(start)+offset) % len(ESWN_dict.keys())]
      經過 Python 的努力,我們現在知道「西,87」,其實就跟從西開始走 3 步是相同意思。往西走 3 步,是要走到哪裡呢?因為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]
      透過上一行的操作,我們知道「西,87」最後走到了南。接下來就簡單啦,去字典查查看南要做什麼動作?好好給我來個後空翻吧草泥馬。

  下面我要偷渡一個新的物件,叫做函數(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的函數,startsteps是這個函數要接受的參數。把參數丟進函數裡,經過一些操作,最後函數會回傳(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]: {'東': '前滾翻', '南': '後滾翻', '西': '前空翻', '北': '後空翻'}

  做蠢事了我。

集合(set)及其基本操作

  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 上了(Githubnbviewer),有興趣的或覺得我講得不夠清楚的,可以把程式碼下載下來玩玩!或是參考網路上強者大大提供的 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 按鈕


上一篇
第 04 天:Python:資料結構與基本操作(一)
下一篇
第 06 天:Python:常用句型
系列文
從LINE BOT到資料視覺化:賴田捕手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
jerry_180801
iT邦新手 5 級 ‧ 2020-03-23 19:00:10

非常謝謝版主與草泥馬們的教學~因為是初次學習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的操作。

希望這麼說明能更好理解一些!

我要留言

立即登入留言