任務
給定一個字典,此字典將不同的鍵映射到不同的值。而你想創建一個反轉的字典,將各個值反映射到鍵。
解決方案
可以創建一個函數,此函數傳遞一個列表推導作為dict的參數以創建需要的字典。
def invert_dict(d):return dict([(v,k) for k,v in d.iteritems() ])
對于比較大的字典,用 Python 標準庫 itertools 模塊提供的 izip 會更快一些:
from itertools import izip
def invert_dict_fast(d):return dict(izip(d.itervalues(),d.iterkeys()))
討論
如果字典d中的值不是獨一無二的,那么d無法被真正地反轉,也就是不存在這樣的字典,對于任意給定的鍵k,滿足id[d[k]]==k。不過,本節展示的函數在這種情況下仍然能夠創建一個“偽反轉”字典 pd,對于任何屬于字典d地值 v,d[pd[v]]==v。如果給你原始的字典 d,以及用本節函數獲得的字典x,可以很容易地檢査x是d的反轉字典還是偽反轉字典:當且僅當 len(x)==len(d)時,x才是d的真正的反轉字典。這是因為,如果兩個不同的鍵對應相同的值,對于解決方案給出的兩個函數來說,兩個鍵中的個一定會消失,因而生成的偽反轉字典的長度也會比原字典的長度短。在任何情況下只有當d中的值是可哈希(hashable,意味著可以用它們做字典的鍵)的,前面展示的函數才能正常工作,否則,函數會拋出一個TypeError 異常。
當我們編寫 Python程序時,我們通常會“無視小的優化”,正如DonaldKnuth在 30年前所說的“比起速度,我們更珍視清晰和正確性。”不過,了解更多讓程序變快的知識也沒有害處:當我們為了簡單和清晰而采用某種方法編寫程序時,我們最好深入地考慮一下我們的決定,不要懵懵懂懂。
在這里,解決方案中的 invent_dict 函數可能會被認為更清晰,因為它清楚地表達了它在做的事。該函數取得了由iteritems方法生成的成對的鍵及其對應值k和v,將它們包裹成(value,key)的順序,并把最后生成的序列作為參數賦給 dict,這樣 dict 就構建出了一個值成為鍵,而原先的鍵變成了對應值的新字典——正是我們需要的反轉字典。
而解決方案中 invert_dict_fast 函數其實也沒有那么復雜,它的操作更加抽象,它首先將所有的鍵和值分別轉為兩個獨立的迭代器,再通過調用Python 標準庫itertools 模塊提供的 izip 將兩個迭代器轉化為一個迭代器,其中每個元素都是像(value,key)一樣的一對值。如果你能夠習慣于這種抽象層次,你將體會到更高層次的簡潔和清晰。
由于這種高度的抽象性,以及不具化(materialize)整個列表(而是通過生成器和迭代器一次生成一項)的特性,invert_dict_fast能夠比invert_dict 快很多。比如,在我的計算機上,反轉10000個條目的字典,invertdict耗時63ms,而invert_dict_fast 則僅用時 20ms。速度提升了3倍,頗為可觀。當你處理大規模數據時,由于代碼的高度抽象性而帶來的性能提升將會變得更加明顯。特別是當你使用itertools來替換循環和列表推導時,執行速度同樣也能獲得極大提升,因為你無須在內存中具化一些超大的列表。當你習慣了更高的抽象層次,性能的提升只是一個額外收益,除此之外,你在觀念和創造性上也會有所進步。