笨辦法學 Python · 續(xù) 練習 17:字典

練習 17:字典

原文:Exercise 17: Dictionary

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

自豪地采用谷歌翻譯

你應(yīng)該熟悉 Python 的dict類。無論什么時候,你編寫這樣的代碼:

cars = {'Toyota': 4, 'BMW': 20, 'Audi': 10}

你在使用字典,將車的品牌(“豐田”,“寶馬”,“奧迪”)和你有的數(shù)量(4,20,10)關(guān)聯(lián)起來。現(xiàn)在使用這種數(shù)據(jù)結(jié)構(gòu)應(yīng)該是你的第二本能,你可能甚至不考慮它是如何工作的。在本練習中,你將通過從已經(jīng)創(chuàng)建的數(shù)據(jù)結(jié)構(gòu),實現(xiàn)自己的Dictionary來了解dict的工作原理。你在本練習中的目標是,根據(jù)我在這里寫的代碼實現(xiàn)自己的Dictionary版本。

挑戰(zhàn)性練習

在本練習中,你將完全記錄并理解我編寫的一段代碼,然后盡可能地,根據(jù)記憶編寫自己的版本。本練習的目的是,學習剖析和理解復雜的代碼。能夠內(nèi)在化或記憶,如何創(chuàng)建一個簡單的數(shù)據(jù)結(jié)構(gòu)(如字典)是很重要的性。我發(fā)現(xiàn),學習剖析和理解一段代碼的最好方法是,根據(jù)自己的學習和記憶來重新實現(xiàn)它。

將其看做一個“原件”類。原件來自繪畫,其中你繪制一幅由他人創(chuàng)作的畫,優(yōu)于創(chuàng)作它的副本。這樣做會教你如何繪畫并且提高你的技能。代碼和繪畫是相似的,因為所有的信息都為復制準備好了,所以你可以通過復制他們的工作,輕松地向別人學習。

制作一份“代碼大師的副本”

要創(chuàng)建一份“代碼大師副本”,你將遵循這個的流程,我稱之為 CASMIR 流程:

  • 復制代碼,使其正常工作。你的副本應(yīng)該完全一樣。這有助于你了解它,并強制你仔細研究它。
  • 使用注釋來標注代碼,并為所有代碼寫一個分析,確保你了解每一行以及它的作用。這可能涉及到你編寫的其他代碼,來將整個概念結(jié)合在一起。
  • 使用簡潔的說明,為這個代碼的工作原理總結(jié)一般結(jié)構(gòu)。這是函數(shù)列表和每個函數(shù)的作用。
  • 記住這個算法和關(guān)鍵代碼段的簡潔描述。
  • 根據(jù)記憶實現(xiàn)可以實現(xiàn)的東西,當你用盡細節(jié)時,回顧你的筆記和原始代碼來記住更多內(nèi)容。
  • 當你需要從你的記憶中復制的時候,重復此過程多次。你的記憶中的副本并不必須是完全一樣的,但應(yīng)接近,并通過你創(chuàng)建的相同測試。

這樣做將使你深入了解數(shù)據(jù)結(jié)構(gòu)的工作原理,但更為重要的是,幫助你內(nèi)在化和回憶此數(shù)據(jù)結(jié)構(gòu)。你終將能夠理解該概念,并在需要創(chuàng)建數(shù)據(jù)結(jié)構(gòu)時實現(xiàn)數(shù)據(jù)結(jié)構(gòu)。這也是訓練你的大腦,在未來記住其他的數(shù)據(jù)結(jié)構(gòu)和算法。

警告

我要做的唯一的警告是,這是一個很簡單,愚蠢,緩慢的Dictionary實現(xiàn)。你真的復制了一個簡單愚蠢的Dictionary,它具有所有的基本元素和作用,但需要大量改進來用于生產(chǎn)。當我們到達練習 19 并研究性能調(diào)整時,會進行這些改進。現(xiàn)在,只需實現(xiàn)這個簡單的版本,就可以了解數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)知識。

復制代碼

首先我們查看Dictionary的代碼,你需要復制它:

from dllist import DoubleLinkedList

class Dictionary(object):
    def __init__(self, num_buckets=256):
        """Initializes a Map with the given number of buckets."""
        self.map = DoubleLinkedList()
        for i in range(0, num_buckets):
            self.map.push(DoubleLinkedList())

    def hash_key(self, key):
        """Given a key this will create a number and then convert it to
        an index for the aMap's buckets."""
        return hash(key) % self.map.count()

    def get_bucket(self, key):
        """Given a key, find the bucket where it would go."""
        bucket_id = self.hash_key(key)
        return self.map.get(bucket_id)

    def get_slot(self, key, default=None):
        """
        Returns either the bucket and node for a slot, or None, None
        """
        bucket = self.get_bucket(key)

        if bucket:
            node = bucket.begin
            i = 0

            while node:
                if key == node.value[0]:
                    return bucket, node
                else:
                    node = node.next
                    i += 1

        # fall through for both if and while above
        return bucket, None

    def get(self, key, default=None):
        """Gets the value in a bucket for the given key, or the default."""
        bucket, node = self.get_slot(key, default=default)
        return node and node.value[1] or node

    def set(self, key, value):
        """Sets the key to the value, replacing any existing value."""
        bucket, slot = self.get_slot(key)

        if slot:
            # the key exists, replace it
            slot.value = (key, value)
        else:
            # the key does not, append to create it
            bucket.push((key, value))

    def delete(self, key):
        """Deletes the given key from the Map."""
        bucket = self.get_bucket(key)
        node = bucket.begin

        while node:
            k, v = node.value
            if key == k:
                bucket.detach_node(node)
                break

    def list(self):
        """Prints out what's in the Map."""
        bucket_node = self.map.begin
        while bucket_node:
            slot_node = bucket_node.value.begin
            while slot_node:
                print(slot_node.value)
                slot_node = slot_node.next
            bucket_node = bucket_node.next

該代碼使用你現(xiàn)有的DoubleLinkedList代碼來實現(xiàn)dict數(shù)據(jù)結(jié)構(gòu)。如果你不完全了解DoubleLinkedList,那么你應(yīng)該嘗試使用代碼復制過程,讓我們更好地理解它。一旦你確定你了解DoubleLinkedList,你可以鍵入此代碼并使其正常工作。記住,在開始標注之前,它必須是完美的副本。你可以做的最糟糕的事情,是標注我的代碼的破損或不正確的副本。

為了幫助你獲得正確的代碼,我寫了一個快速和簡陋的小型測試腳本:

from dictionary import Dictionary

# create a mapping of state to abbreviation
states = Dictionary()
states.set('Oregon', 'OR')
states.set('Florida', 'FL')
states.set('California', 'CA')
states.set('New York', 'NY')
states.set('Michigan', 'MI')

# create a basic set of states and some cities in them
cities = Dictionary()
cities.set('CA', 'San Francisco')
cities.set('MI', 'Detroit')
cities.set('FL', 'Jacksonville')

# add some more cities
cities.set('NY', 'New York')
cities.set('OR', 'Portland')


# print(out some cities
print('-' * 10)
print("NY State has: %s" % cities.get('NY'))
print("OR State has: %s" % cities.get('OR'))

# print(some states
print('-' * 10)
print("Michigan's abbreviation is: %s" % states.get('Michigan'))
print("Florida's abbreviation is: %s" % states.get('Florida'))

# do it by using the state then cities dict
print('-' * 10)
print("Michigan has: %s" % cities.get(states.get('Michigan')))
print("Florida has: %s" % cities.get(states.get('Florida')))

# print(every state abbreviation
print('-' * 10)
states.list()

# print(every city in state
print('-' * 10)
cities.list()

print('-' * 10)
state = states.get('Texas')

if not state:
  print("Sorry, no Texas.")

# default values using ||= with the nil result
# can you do this on one line?
city = cities.get('TX', 'Does Not Exist')
print("The city for the state 'TX' is: %s" % city)

我希望你也可以正確地鍵入這個代碼,但是當你進入大師副本的下一個階段時,你會把它變成一個正式的自動測試,你可以運行pytest。現(xiàn)在,只要讓這個腳本工作,就可以讓Dictionary類工作,之后你可以在下一個階段清理它。

標注代碼

確保我的代碼的副本完全一樣,并通過測試腳本。然后,你可以開始標注代碼,并研究每一行來了解其作用。一個非常好的方式是,編寫一個“正式”的自動化測試,并在你工作時標注代碼。獲取dictionary_test.py腳本,并將每個部分轉(zhuǎn)換成一個小型測試函數(shù),然后標注Dictionary類。

例如,test_dictionary.py中的第一部分測試創(chuàng)建一個字典,并執(zhí)行一系列Dictionary.set調(diào)用。我會把它轉(zhuǎn)換成一個test_set函數(shù),然后在dictionary.py文件中標注Dictionary.set函數(shù)。當你標注Dictionary.set函數(shù)時,你必須潛入到Dictionary.get_slot函數(shù)中,然后是Dictionary.get_bucket函數(shù),最后是Dictionary.hash_key。這迫使你通過一個測試和有組織的方式,來標注和了解Dictionary類的大段代碼。

總結(jié)數(shù)據(jù)結(jié)構(gòu)

你現(xiàn)在可以總結(jié)你在dictionary.py中,通過標注代碼所學到的內(nèi)容,并將dictionary_test.py文件重寫為真正的pytest自動測試。你的摘要應(yīng)該是數(shù)據(jù)結(jié)構(gòu)的清晰和細微描述。如果你可以把它寫在一張紙上,那么你做得很好。并不是所有的數(shù)據(jù)結(jié)構(gòu)都可以簡明扼要地總結(jié)出來,但是保持摘要簡潔將有助于你記住它。你可以使用圖表,圖紙,單詞,或你能夠記住的任何內(nèi)容。

此摘要的目的是為你提供一組快速注解,你可以“掛載”更多的細節(jié),當你的記憶進行到下一步的時候。摘要不一定包括所有內(nèi)容,但應(yīng)該包括一些細節(jié),可以觸發(fā)你對“標注”階段的代碼的記憶,從而觸發(fā)你對“復制”階段的記憶。這被稱為“分塊”,你可以將更詳細的記憶和信息附加到信息的細微碎片。在撰寫摘要時記住這一點。少即是多,但太少沒有用。

記憶摘要

你可以用任何方式記住摘要和帶標注的代碼,但我將給出一個基本的記憶過程,你可以使用它。老實說,記住復雜的東西是每個人的不斷嘗試和犯錯的過程,但有些技巧有幫助:

  • 確保你有一個紙質(zhì)的筆記本,以及摘要和代碼的打印。
  • 花3分鐘,只需閱讀摘要并嘗試記住它。靜靜地看著它,大聲讀出來,然后閉上眼睛,重復你所讀的內(nèi)容,甚至嘗試記住紙上的單詞的“形狀”。聽起來很愚蠢,但相信我,它完全奏效。記住你的大腦比你想象的更好。
  • 把摘要翻過來,并嘗試從你記住的內(nèi)容中再次寫出來,當你卡住時,將其快速翻過來并查看。在你快速瞥見之后,把摘要翻過來,并嘗試完成更多。
  • 一旦從(大部分)記憶中寫出了摘要的副本,請使用摘要,花另一個 3 分鐘,試圖記住帶標注的代碼。僅僅閱讀摘要的一部分,然后再看看代碼的相關(guān)部分,并嘗試記住它。甚至每個函數(shù)只能花 3 分鐘。
  • 一旦你花時間試圖記住帶標注的代碼,把它翻過去,使用摘要,嘗試回憶你筆記本中的代碼。同樣,當你陷入困境時,快速把標注翻過來并查看。
  • 繼續(xù)這樣做,直到你可以在紙上寫出代碼的完整副本。你紙上的代碼不一定是完美的 Python 代碼,但應(yīng)該非常接近原始代碼。

看起來這可能是無法實現(xiàn),但是當你這么做時,你會感到驚訝。完成此操作后,你也會驚訝于你了解了字典的概念。這不是簡單的記憶,而是建立一個概念圖,當你嘗試自己實現(xiàn)字典時,你可以實際使用它。

警告

如果你是那種擔心記不住任何東西的人,那么這個練習會為你將來帶來巨大的幫助。能夠遵循流程來記住某些東西,有助于克服任何記憶的挫折。你并不是沉浸在“失敗”中,而是可以在堅持中看到緩慢的改進。當你這樣做,你會看到改善你的回憶的方式和黑魔法,并且你會做得更好。你只需要相信我,這似乎是一種緩慢的學習方式,但它比其他技術(shù)要快得多。

從記憶中實現(xiàn)

現(xiàn)在是時候走到你的電腦旁邊 - 把你的紙質(zhì)筆記放在另一個房間或地板上 - 并根據(jù)記憶嘗試你的第一個實現(xiàn)。你的第一次嘗試可能完全是一場災難,也可能完正確。你最可能不習慣從記憶中實現(xiàn)任何東西。只要放下任何你記得的東西,當你到達你的記憶的彼端,回到另一個房間,記憶更多東西。經(jīng)過幾次到你的記憶空間的旅行,你會進入它,記憶會更好地流出來。你完全可以再次訪問你的記憶筆記。這一切都關(guān)于,試圖保持代碼的記憶并提高自己的技能。

我建議你首先寫下你的想法,無論是測試,代碼還是兩者。然后使用你可以回憶的內(nèi)容,來實現(xiàn)或回憶代碼的其他部分。如果你首先坐下來并記住test_set函數(shù)名和幾行代碼,然后把它們寫下來。當他們在你的頭腦中,立即利用它們。一旦你完成了,盡你最大的努力,使用這個測試來記住或?qū)崿F(xiàn)Dictionary.set函數(shù)。你的目標是使用任何信息來構(gòu)建或者其它信息。

你也應(yīng)該嘗試,用你對Dictionary的理解來實現(xiàn)代碼。不要簡單以攝影方式來回憶每一行。這實際上是不可能的,因為沒有人有攝影記憶(去查一下,沒有人)。大多數(shù)人的記憶都不錯,能夠觸發(fā)他們可以使用的概念性理解。你應(yīng)該做同樣的事情,并使用你的Dictionary的知識來創(chuàng)建自己的副本。在上面的示例中,你知道Dictionary.set以某種方式運行,你需要一種方法來獲取插槽(鏈表節(jié)點)和桶(鏈表本身)...所以這意味著,你需要get_slotget_bucket。你不是以攝影方式記住每個字符;而是記住所有關(guān)鍵概念并使用它們。

重復

這個練習最重要的部分是,重復幾次這個流程,使其沒有錯誤,才能使其更好。你會對這本書中的其他數(shù)據(jù)結(jié)構(gòu)這樣做,所以你會得到大量的練習。如果你必須回去記憶 100 次才行,也是可以的。最終你只需要做 50 遍,然后下一次只有 10 遍,然后最終你將能夠輕易從記憶中實現(xiàn)一個Dictionary。盡管繼續(xù)嘗試,并嘗試像冥想那樣接近它,所以你這樣做的時候可以放松。

深入學習

  • 我的測試非常有限。寫一個更廣泛的測試。
  • 練習 16 的排序算法如何有助于這個數(shù)據(jù)結(jié)構(gòu)?
  • 當你將鍵和值隨機化,用于這個數(shù)據(jù)結(jié)構(gòu)時,會發(fā)生什么?排序算法有幫助嗎?
  • num_buckets對數(shù)據(jù)結(jié)構(gòu)有什么影響?

破壞它

你的大腦可能宕機了,要休息一下,然后嘗試破壞這個代碼。這個實現(xiàn)很容易被數(shù)據(jù)淹沒和壓倒。奇怪的邊界情況如何呢?你可以將任何東西添加為一個鍵,或者只是字符串?會造成什么問題?最后,你是否可以對代碼暗中耍一些花招,使其看起來像是正常工作,但實際上是以一些機智的方式來破壞它?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,728評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 練習 19:改善性能 原文:Exercise 19: Improving Performance 譯者:飛龍 協(xié)議...
    布客飛龍閱讀 420評論 0 1
  • 年底總是得出來冒冒泡,否則這存在感就越來越低了。長話短說,15年完成的主要是下面幾個事情: 買了房。一入房奴深似海...
    ifcode閱讀 319評論 0 1
  • 你出生的時候,天上的月亮又大又圓,所以你叫明月。 小姑娘每次問爸爸自己名字由來的時候,爸爸總是寵溺得看著她,用帶有...
    歌德愛科技閱讀 530評論 1 3