跳轉到內容

像計算機科學家一樣思考:用 Python 學習 第 2 版/字典

來自華夏公益教科書,開放書籍,開放世界

到目前為止,我們詳細研究的所有複合資料型別——字串、列表和元組——都是序列型別,它們使用整數作為索引來訪問它們包含的值。

字典是一種不同的複合型別。它們是 Python 內建的 對映型別。它們將 (可以是任何不可變型別)對映到值,值可以是任何型別,就像列表或元組的值一樣。

例如,我們將建立一個字典將英文單詞翻譯成西班牙語。對於這個字典,鍵是字串。

建立字典的一種方法是從空字典開始並新增 鍵值對。空字典用{}:

第一個賦值建立一個名為eng2sp的字典;其他賦值向字典新增新的鍵值對。我們可以像往常一樣列印字典的當前值

字典的鍵值對用逗號分隔。每個對包含一個用冒號分隔的鍵和值。

對的順序可能不是你期望的。Python 使用複雜演算法來確定鍵值對在字典中的儲存位置。就我們而言,我們可以認為這種排序是不可預測的。

建立字典的另一種方法是使用與先前輸出相同的語法提供鍵值對列表

我們寫對的順序無關緊要。字典中的值是透過鍵訪問的,而不是透過索引訪問的,因此無需關心排序。

以下是我們使用鍵查詢對應值的示例

'two'產生值'dos'.

字典操作

[編輯 | 編輯原始碼]

del語句從字典中刪除鍵值對。例如,以下字典包含各種水果的名稱以及庫存中每種水果的數量

如果有人買了所有梨,我們可以從字典中刪除該條目

或者,如果我們很快會收到更多梨,我們可能只需要更改與梨關聯的值

len函式也適用於字典;它返回鍵值對的數量

字典方法

[編輯 | 編輯原始碼]

字典有一些有用的內建方法。

keys方法接受字典並返回其鍵的列表。

正如我們之前在字串和列表中看到的,字典方法使用點表示法,它指定方法的名稱位於點的右側,而應用方法的物件的名稱位於點的左側。圓括號表示該方法不接受任何引數。

方法呼叫稱為 呼叫;在這種情況下,我們會說我們在keys方法上呼叫eng2sp物件。正如我們將在幾章後討論面向物件程式設計時看到的那樣,方法呼叫的物件實際上是方法的第一個引數。

values方法類似;它返回字典中值的列表

items方法同時返回兩者,以元組列表的形式——每個鍵值對一個。

has_key方法接受鍵作為引數並返回True如果鍵出現在字典中,則返回False否則

此方法非常有用,因為在字典中查詢不存在的鍵會導致執行時錯誤

別名和複製

[編輯 | 編輯原始碼]

由於字典是可變的,因此您需要了解別名。當兩個變數引用同一個物件時,對其中一個變數的更改會影響另一個變數。

如果您想修改字典並保留原始字典的副本,請使用copy方法。例如,opposites是一個包含相反詞對的字典

aliasopposites引用同一個物件;copy引用同一個字典的新副本。如果我們修改alias, opposites也會發生改變

如果我們修改copy, opposites保持不變

稀疏矩陣

[編輯 | 編輯原始碼]

我們之前使用列表的列表來表示矩陣。對於大多數非零值的矩陣來說,這是一個不錯的選擇,但請考慮以下 稀疏矩陣

稀疏矩陣 列表表示包含許多零

另一種方法是使用字典。對於鍵,我們可以使用包含行號和列號的元組。以下是同一矩陣的字典表示

我們只需要三個鍵值對,每個非零元素一個。每個鍵都是一個元組,每個值都是一個整數。

要訪問矩陣的元素,我們可以使用[]運算子

請注意,字典表示的語法與巢狀列表表示的語法不同。我們不使用兩個整數索引,而是使用一個索引,它是一個包含整數的元組。

有一個問題。如果我們指定一個為零的元素,我們會收到錯誤,因為字典中沒有包含該鍵的條目

get方法解決了這個問題

第一個引數是鍵;第二個引數是get如果鍵不在字典中應該返回的值

get絕對改進了訪問稀疏矩陣的語義。語法有點可惜。

如果您使用上一章中的fibonacci函式進行了一些操作,您可能已經注意到,您提供的引數越大,函式執行的時間就越長。此外,執行時間增長得非常快。在我們的一臺機器上,fibonacci(20)立即完成,fibonacci(30)大約需要一秒鐘,而fibonacci(40)則大約永遠不會完成。

為了理解原因,請考慮以下fibonacci呼叫圖,其中n = 4:

斐波那契樹 呼叫圖顯示一組函式幀,用線連線每個幀與其呼叫的函式的幀。在圖的頂部,fibonacci呼叫圖,其中n = 4呼叫fibonacci呼叫圖,其中n = 3n = 2。反過來,fibonacci呼叫圖,其中n = 3呼叫fibonacci呼叫圖,其中n = 2n = 1。依此類推。

統計fibonacci(0)fibonacci(1)被呼叫的次數。這是一個低效的解決方案,隨著引數的增大,效率會變得更糟。

一個好的解決方案是將已經計算過的值儲存在一個字典中,以便跟蹤它們。儲存起來以便將來使用的先前計算過的值稱為提示。以下是用提示的實現方法:fibonacci使用提示

名為previous的字典跟蹤我們已經知道的斐波那契數。我們從兩個鍵值對開始:0 對映到 1;1 對映到 1。

無論何時fibonacci被呼叫,它都會檢查字典以確定它是否包含結果。如果在字典中,函式可以立即返回而無需進行任何進一步的遞迴呼叫。如果沒有,它必須計算新值。新值在函式返回之前新增到字典中。

使用此版本的fibonacci,我們的機器可以在眨眼間計算fibonacci(100)

L在數字的末尾表示它是一個long整數。

長整數

[edit | edit source]

Python 提供了一種稱為long的型別,它可以處理任何大小的整數(僅受計算機記憶體大小的限制)。

有三種方法可以建立long值。第一種是計算一個太大而無法放入int中的算術表示式。我們已經在上面的fibonacci(100)示例中看到了這一點。另一種方法是在數字末尾寫一個大寫的L。第三種方法是呼叫

,並將要轉換的值作為引數傳遞給它。long,就像longfloatint一樣,可以將intfloat,甚至數字字串轉換為長整數統計字母

[edit | edit source]

在第 7 章中,我們編寫了一個函式來統計字串中字母出現的次數。這個問題的更通用版本是形成字串中字母的直方圖,即每個字母出現的次數。

這樣的直方圖可能對壓縮文字檔案很有用。由於不同的字母出現的頻率不同,我們可以透過對常用字母使用較短的程式碼,對出現頻率較低的字母使用較長的程式碼來壓縮檔案。

字典提供了一種優雅的方法來生成直方圖

我們從一個空的字典開始。對於字串中的每個字母,我們找到當前計數(可能是零)並遞增它。最後,字典包含字母及其頻率的配對。

以字母順序顯示直方圖可能更吸引人。我們可以使用

sortitems方法來做到這一點案例研究:機器人

[edit | edit source]

遊戲

[edit | edit source]

在這個案例研究中,我們將編寫一個經典的基於控制檯的遊戲 robots 的版本。

Robots 是一款回合制遊戲,玩家扮演主角,試圖在被愚蠢但執著的機器人追捕時生存下來。每次玩家移動時,每個機器人都會向玩家移動一格。如果機器人抓住玩家,玩家就死了,但如果機器人相互碰撞,它們就會死掉,留下宕機器人的殘骸。如果其他機器人與機器人殘骸碰撞,它們也會死掉。

基本策略是讓玩家自己處於一個位置,這樣機器人就可以在向玩家移動時相互碰撞,以及與機器人殘骸碰撞。為了使遊戲更具可玩性,玩家還被賦予了將自己傳送到螢幕上另一個位置的能力——安全傳送 3 次,之後隨機傳送,這樣玩家就不會一直被逼到角落裡,然後每次都輸掉。

設定世界、玩家和主迴圈

[edit | edit source]

讓我們從一個將玩家放置在螢幕上並具有一個函式來響應按鍵來移動玩家的程式開始

像這樣的程式,它們涉及透過事件(例如按鍵和滑鼠點選)與使用者互動,被稱為事件驅動程式

此階段的主要事件迴圈只是

事件處理是在

move_player函式中完成的。update_when('key_pressed')等待按鍵按下,然後才執行下一個語句。多路分支語句然後處理與遊戲相關的全部按鍵。按下 Esc 鍵會導致

返回函式中完成的。,使Truenot finished為 false,從而退出主迴圈並結束遊戲。4、7、8、9、6、3、2 和 1 鍵都導致玩家在適當的方向移動,前提是她沒有被視窗邊緣擋住。新增機器人

[edit | edit source]

現在讓我們新增一個機器人,每次玩家移動時,它都會朝玩家前進。

place_player函式之間新增以下place_robot函式。在函式中完成的。:

place_player函式之後立即新增move_robot函式中完成的。:

函式。我們需要將機器人和玩家都傳遞給此函式,以便它可以比較它們的位置並將機器人移動到玩家的位置。

現在在程式主體中,在player = place_player()行之後立即新增robot = place_robot()行,並在主迴圈中,在finished = move_player(player)行之後立即新增move_robot(robot, player).

呼叫。

檢查碰撞

[edit | edit source]

現在我們有一個機器人,它不斷地向玩家移動,但一旦它抓住玩家,它就會一直跟隨玩家,無論玩家去哪裡。我們希望發生的事情是在玩家被抓住後立即結束遊戲。以下函式將確定這種情況是否已經發生函式中完成的。將這個新函式放在函式的正下方。現在讓我們修改play_game

函式來檢查碰撞我們將變數finished重新命名為defeated,它現在被設定為collided重新命名為的結果。主迴圈只要為 false 就會執行。按下 Esc 鍵仍然會結束程式,因為我們檢查了quit重新命名為並退出主迴圈,如果它為 true。最後,我們在主迴圈之後立即檢查

,如果它為 true,則顯示相應的提示資訊。

新增更多機器人

[edit | edit source]

  • 接下來我們可以做幾件事
  • 讓玩家有能力傳送到另一個位置來逃脫追捕。
  • 為玩家提供安全的放置位置,這樣它永遠不會從機器人身上開始。

新增更多機器人。

新增將玩家傳送至隨機位置的能力是最簡單的任務,我們已將其留給讀者作為練習。

我們如何為玩家提供安全的放置位置,將取決於我們如何表示多個機器人,因此有必要先解決新增更多機器人的問題。要新增第二個機器人,我們可以建立一個名為robot2函式之間新增以下的另一個變數,並在其中呼叫另一個

。此方法的問題在於,我們很快就會想要很多機器人,而為所有機器人取名將會很麻煩。更優雅的解決方案是將所有機器人放在一個列表中函式之間新增以下現在,我們不再在函式的正下方。現在讓我們修改中呼叫,而是呼叫place_robots

,它返回一個包含所有機器人的單個列表

place_player在放置了多個機器人之後,我們必須處理每個機器人的移動。但是,我們已經解決了移動單個機器人的問題,因此遍歷列表並依次移動每個機器人即可解決問題move_robot函式之後立即新增move_robots函式的正下方。現在讓我們修改,並將在放置了多個機器人之後,我們必須處理每個機器人的移動。但是,我們已經解決了移動單個機器人的問題,因此遍歷列表並依次移動每個機器人即可解決問題更改為呼叫函式之後立即新增.

,而不是呼叫

place_player現在,我們需要檢查每個機器人是否與玩家發生了碰撞move_robot,它現在被設定為check_collisions函式的正下方。現在讓我們修改,並將重新命名為,並將現在,我們需要檢查每個機器人是否與玩家發生了碰撞更改為呼叫,它現在被設定為.

中的行更改為最後,我們需要遍歷robots重新命名為,如果

變為 true,則依次移除每個機器人。我們已將新增此功能留作練習。

贏得遊戲

我們遊戲中最大的問題是無法獲勝。機器人既無情又堅不可摧。透過謹慎的操作和一點幸運的傳送,我們可以達到看起來只有一個機器人追逐玩家的點(實際上所有機器人都會疊在一起)。這堆移動的機器人會繼續追逐我們倒黴的玩家,直到它被抓住,無論是我們的一次糟糕的移動還是傳送將玩家直接傳送到機器人身上。

當兩個機器人發生碰撞時,它們應該死亡,留下一個垃圾堆。機器人(或玩家)在與垃圾堆碰撞時也應該死亡。實現這個邏輯相當棘手。在玩家和每個機器人移動之後,我們需要

  1. 檢查玩家是否與機器人或垃圾堆發生了碰撞。如果發生碰撞,則將重新命名為設定為true,並退出遊戲迴圈。
  2. 檢查每個機器人最後,我們需要遍歷列表,看它是否與垃圾堆發生了碰撞。如果發生碰撞,則忽略該機器人(將其從最後,我們需要遍歷列表中刪除)。
  3. 檢查每個剩餘的機器人,看它們是否與另一個機器人發生了碰撞。如果發生碰撞,則丟棄所有發生碰撞的機器人,並在它們所佔據的位置放置一個垃圾堆。
  4. 檢查是否還有機器人。如果沒有,則結束遊戲並標記玩家為獲勝者。

讓我們依次完成這些任務。

新增垃圾

[編輯 | 編輯原始碼]

大部分工作將在我們的現在,我們需要檢查每個機器人是否與玩家發生了碰撞函式中進行。讓我們從修改,它現在被設定為開始,將引數名稱更改為反映其更通用的用途

我們現在在呼叫垃圾之後立即引入一個名為,而是呼叫:

的新空列表,並修改現在,我們需要檢查每個機器人是否與玩家發生了碰撞以包含新列表

請務必修改對現在,我們需要檢查每個機器人是否與玩家發生了碰撞(目前為defeated = check_collisions(robots, player))的呼叫,以包含垃圾作為新的引數。

同樣,我們需要修復if defeated之後的邏輯,在顯示“They got you!”訊息之前將新的垃圾從螢幕上移除

由於此時垃圾始終為空列表,因此我們沒有改變程式的行為。為了測試我們的新邏輯是否真正有效,我們可以引入一個垃圾堆,讓我們的玩家撞向它,此時遊戲應該從螢幕上刪除所有專案,並顯示結束訊息。

為了測試,臨時修改我們的程式,將機器人和玩家的隨機放置位置更改為預定的位置會很有幫助。我們計劃使用實心方塊來表示垃圾堆。我們觀察到放置機器人與放置垃圾堆非常相似,並修改函式之間新增以下來同時執行這兩個操作

注意xy現在是引數,以及一個新的引數,我們將使用它來設定filled為垃圾堆設定為true。

我們的程式現在出現了問題,因為,而是呼叫finished函式之間新增以下中的呼叫沒有為xy傳遞引數。修復這個問題以及設定程式進行測試留作練習。

移除撞到垃圾的機器人

[編輯 | 編輯原始碼]

為了移除與垃圾堆發生碰撞的機器人,我們在現在,我們需要檢查每個機器人是否與玩家發生了碰撞中新增一個巢狀迴圈,遍歷每個機器人和每個垃圾堆。我們第一次嘗試這樣操作,但它沒有成功

執行這個新程式碼,使用練習 11 中設定的程式,我們發現了一個錯誤。看起來機器人仍然像以前一樣穿透垃圾堆。

實際上,錯誤更微妙一些。由於我們有兩個機器人疊在一起,當檢測到第一個機器人的碰撞並將該機器人移除時,我們將第二個機器人移到列表中的第一個位置,並且它被下一個迭代所忽略。在遍歷列表時修改列表通常很危險。這樣做可能會在程式中引入許多難以查詢的錯誤。

在這種情況下,解決方法是反向遍歷最後,我們需要遍歷列表,這樣當我們從列表中移除一個機器人時,所有由於索引更改而導致列表索引更改的機器人都是我們已經評估過的機器人。

像往常一樣,Python 提供了一種優雅的方法來實現這一點。內建函式reversed提供對序列的反向迭代。替換

呼叫圖,其中

將使我們的程式按照我們的預期方式工作。

將機器人變成垃圾並讓玩家獲勝

[編輯 | 編輯原始碼]

我們現在要檢查每個機器人,看它是否與其他任何機器人發生了碰撞。我們將移除所有發生碰撞的機器人,在其身後留下一個垃圾堆。如果我們到達一個不再有機器人的狀態,則玩家獲勝。

再一次,我們必須小心,不要引入與從我們正在迭代的列表中移除專案相關的錯誤。

以下是計劃

  1. 檢查最後,我們需要遍歷中的每個機器人(一個外部迴圈,向前遍歷)。
  2. 將其與每個後面的機器人進行比較(一個內部迴圈,向後遍歷)。
  3. 如果兩個機器人發生了碰撞,則在它們的位置新增一個垃圾,標記第一個機器人為垃圾,並移除第二個機器人。
  4. 檢查完所有機器人的碰撞之後,再次反向遍歷機器人列表,移除所有標記為垃圾的機器人。
  5. 檢查是否還有機器人。如果沒有,則宣佈玩家獲勝。

將以下內容新增到現在,我們需要檢查每個機器人是否與玩家發生了碰撞將完成我們需要做的大部分事情

我們使用enumerate函式(我們在第 9 章中見過),在向前遍歷時獲取每個機器人的索引和值。然後對剩餘機器人的切片進行反向遍歷reversed(robots[index+1:]),設定碰撞檢查。

每當兩個機器人發生碰撞時,我們的計劃要求在該位置新增一塊垃圾,標記第一個機器人以便稍後移除(我們仍然需要它與其他機器人進行比較),並立即移除第二個機器人。的if collided(robot1, robot2)條件語句的主體旨在實現這一點,但如果你仔細觀察這行

,你應該注意到一個問題。robot1['junk']會導致語法錯誤,因為我們的機器人字典還沒有包含'junk'鍵。為了解決這個問題,我們修改函式之間新增以下以適應新的鍵

隨著程式開發的進行,資料結構發生變化並不少見。逐步求精程式資料和邏輯是結構化程式設計過程的正常組成部分。

robot1被標記為垃圾之後,我們使用junk.append(place_robot(robot1['x'], robot1['y'], True))將一個垃圾堆新增到垃圾列表中,並與之位於相同位置,然後從遊戲中移除要新增第二個機器人,我們可以建立一個名為,方法是從圖形視窗中移除其形狀,然後從機器人列表中移除它。

下一個迴圈反向遍歷機器人列表,移除之前標記為垃圾的所有機器人。由於當所有機器人死亡時玩家獲勝,而當機器人列表不再包含存活的機器人時,它將為空,因此我們可以簡單地檢查最後,我們需要遍歷是否為空來確定玩家是否獲勝。

這可以在現在,我們需要檢查每個機器人是否與玩家發生了碰撞中完成,方法是在我們完成檢查機器人碰撞並移除死亡機器人後立即新增

嗯...我們應該返回什麼?在當前狀態下,現在,我們需要檢查每個機器人是否與玩家發生了碰撞是一個布林函式,當玩家與某物發生碰撞並輸掉遊戲時返回true,當玩家沒有輸掉遊戲並且遊戲應該繼續時返回false。這就是為什麼在函式的正下方。現在讓我們修改函式中捕獲返回值的變數被稱為重新命名為.

現在我們有三種可能的狀態

  1. 最後,我們需要遍歷不為空且玩家沒有與任何東西發生碰撞 - 遊戲仍在進行中
  2. 玩家與某物發生了碰撞 - 機器人獲勝
  3. 玩家沒有與任何東西發生碰撞,並且最後,我們需要遍歷為空 - 玩家獲勝

為了儘可能少地更改當前程式來處理這種情況,我們將利用 Python 允許序列型別充當布林值的雙重身份。當遊戲應該繼續時,我們將返回一個空字串(這是 false),並返回"robots_win""player_wins"來處理其他兩種情況。現在,我們需要檢查每個機器人是否與玩家發生了碰撞現在應該看起來像這樣

需要對函式的正下方。現在讓我們修改進行一些相應的更改,以使用新的返回值。這些留作練習。

術語表

[編輯 | 編輯原始碼]
  1. 編寫一個程式,從命令列讀取一個字串,並返回一個表格,其中包含字串中出現的字母按字母順序排列,以及每個字母出現的次數。不區分大小寫。程式的示例執行如下所示

    $ python letter_counts.py "ThiS is String with Upper and lower case Letters."
    a  2
    c  1
    d  1
    e  5
    g  1
    h  2
    i  4
    l  2
    n  2
    o  1
    p  2
    r  4
    s  5
    t  5
    u  1
    w  2
    $
  2. 從一個連續的直譯器會話中,給出 Python 直譯器對以下每個輸入的響應。

    #.
    #.
    #.
    #.
    #.
    #.
    #.

    確保你理解為什麼你會得到每個結果。然後應用你學到的知識來填充下面函式的正文。

    你的解決方案應該透過 doctests。
  3. 編寫一個名為alice_words.py的程式,建立一個名為alice_words.txt的文字檔案,其中包含在 alice_in_wonderland.txt_ 中找到的所有單詞的字母排序列表,以及每個單詞出現的次數。你的輸出檔案的首 10 行應該看起來像這樣

    Word              Count
    =======================
    a                 631
    a-piece           1
    abide             1
    able              1
    about             94
    above             3
    absence           1
    absurd            2
    單詞alice在書中出現了多少次?
  4. 《愛麗絲夢遊仙境》中最長的單詞是什麼?它有多少個字元?
  5. 將程式碼從“設定世界、玩家和主迴圈”部分複製到一個名為robots.py的檔案中並執行它。你應該能夠使用數字鍵盤在螢幕上移動玩家,並透過按下 Esc 鍵退出程式。
  6. 筆記型電腦通常比臺式電腦的鍵盤更小,沒有單獨的數字鍵盤。修改 robots 程式,使其使用 'a'、'q'、'w'、'e'、'd'、'c'、'x' 和 'z' 來代替 '4'、'7'、'8'、'9'、'6'、'3'、'2' 和 '1',以便它可以在典型的筆記型電腦鍵盤上工作。
  7. 將“新增機器人”部分中的所有程式碼新增到指示的位置。確保程式正常工作,並且現在有一個機器人跟隨你的玩家。
  8. 將“檢查碰撞”部分中的所有程式碼新增到指示的位置。驗證程式在機器人抓住玩家後是否結束,並顯示“他們抓住了你!”訊息 3 秒。
  9. 修改函式中完成的。函式,為玩家新增每次按下0鍵時跳到隨機位置的功能。(提示:函式。在已經擁有將玩家放置在隨機位置所需的邏輯。只需新增另一個條件分支到函式中完成的。中,當key_pressed('0')為真時使用此邏輯。)測試程式以驗證你的玩家現在是否可以傳送到螢幕上的隨機位置以擺脫困境。
  10. 對你的程式進行所有在“新增更多機器人”中指示的更改。確保迴圈遍歷最後,我們需要遍歷列表,在重新命名為變為真後,依次刪除每個機器人。測試你的程式以驗證現在是否有兩個機器人追趕你的玩家。讓一個機器人抓住你以測試你是否正確處理了所有機器人的刪除。將robots = place_robots(2)中的引數從 2 改為 4,並確認你有 4 個機器人。
  11. 對你的程式進行所有在“新增 junk”中指示的更改。修復,而是呼叫,將xy的值的隨機生成移動到適當的位置,並將這些值作為引數傳遞給函式之間新增以下的呼叫中。現在,我們準備對我們的程式進行臨時修改,以刪除隨機性,以便我們可以控制它進行測試。我們可以從在遊戲板的中心放置一堆垃圾開始。更改

    finished

    執行程式並確認棋盤中心有一個黑色方塊。現在更改函式。在使其看起來像這樣

    最後,暫時註釋掉xy中的值的隨機生成以及,而是呼叫的建立numbotsrobots。用建立兩個固定位置的機器人的程式碼替換此邏輯

    現在當你啟動程式時,它應該看起來像這樣

    機器人 1

    當你執行此程式,並且保持靜止(透過重複按下5),或者遠離垃圾堆,你可以確認機器人完好無損地穿過它。另一方面,當你進入垃圾堆時,你會死掉。
  12. 函式的正下方。現在讓我們修改進行以下修改,以整合到“將機器人變成垃圾並讓玩家獲勝”中所做的更改。

    1. 重新命名重新命名為finishedwinner並將其初始化為空字串,而不是False.
華夏公益教科書