從零開始學Python-Day12-dict和set

字典,dictionary,簡稱dict,用key-value儲存,方便查找。假設這樣一個問題,需要查詢單個學生的成績,如果用list來做,需要用到兩個list:

<code>花名冊 = ['張三','李四','王五']
成績 = [100,90,80]/<code>

如果要查詢張三的成績,需要現在list花名冊中找到張三所在的位置,然後到list成績中,查詢對應位置的數值,這種方法的話,如果list很長,查詢時間也會相應變長,因為相當於做了兩次查詢。如果用字典來做,只需要查詢一次:

<code>>>> 成績單 = {'張三':100,'李四':90,'王五':80,}
>>> 成績單['張三']
100/<code>

但你會說,如果字典內容很多,查詢也不快啊,這裡就要說到字典的特性了,哈希表。

要理解dict的有關內容需要你理解哈希表(map)的相關基礎知識,這個其實是《算法與數據結構》裡面的內容。

1.list和tuple其實是用鏈表順序存儲的,也就是前一個元素中存儲了下一個元素的位置,這樣只要找到第一個元素的位置就可以順藤摸瓜找到所有元素的位置,所以list的名字其實就是個指針,指向list的第一個元素的位置。list的插入和刪除等可以直接用鏈表的方式進行,比如我要在第1個元素和第2個元素中間插入一個元素,那麼直接在鏈表的最後面(我們假設這個list只有兩個元素,那麼也就是在第3個元素的位置上)插入這個元素,然後把第一個元素指針指向這個元素(第3個位置),然後再把新插入的元素的指針指向原來的第2個元素,這樣插入操作就完成了。讀取這個list的時候,先用list的名字(就是個指針,指向第1個元素的位置)找到第一個元素,然後用第1一個元素的指針找到第2個元素(位置3),然後用第2個元素的指針找到第3個元素(位置2),以此類推。所以list的順序和內存中的實際順序其實不一定完全對應。這種存儲方式不會浪費內存,但查找起來特別費時間,因為要按照鏈表一個一個找下去,如果你的list特別大的話,那麼要等好久才會找到結果。

2.dict則為了快速查找使用了一種特別的方法,哈希表。哈希表採用哈希函數從key計算得到一個數字(哈希函數有個特點:對於不同的key,有很大的概率得到的哈希值也不同),然後直接把value存儲到這個數字所對應的地址上,比如key=’ABC’,value=10,經過哈希函數得到key對應的哈希值為123,那麼就申請一個有1000個地址(從0到999)的內存,然後把10存放在地址為123的地方。類似的,對於key=’BCD’,value=20,得到key的哈希值為234,那麼就把20存放在地址為234的地方。對於這樣的表查找起來是非常方便的。只要給出key,計算得到哈希值,然後直接到對應的地址去找value就可以了。無論有幾個元素,都可以直接找到value,無需遍歷整個表。不過雖然dict查找速度快,但內存浪費嚴重,你看我們只存儲了兩個元素,都要申請一個長度為1000的內存。

3.現在你知道為啥key要用不可變對象了吧?因為不可變對象是常量,每次的哈希值算出來都是固定的,這樣就不會出錯。比如key=’ABC’,value=10,存儲地址為123,假設我突發奇想,把key改成’BCD’,那麼當查找’BCD’的value的時候就會去234的地址找,但那裡啥也沒有,這就亂套了。

3.你看我們上面有一句話:對於不同的key,有很大的概率得到的哈希值也不同。那麼有很小的概率不同的key可以得到相同的哈希值了?沒錯,比如對於我們的例子來說,哈希值只有3位,那麼只要元素個數超過1000,就一定會有至少兩個key的哈希值相同(鴿籠原理),這種情況叫“衝突”,設計哈希表的時候要採取辦法減少衝突,實在衝突了也要想辦法補救。不過這是編譯器的事情,況且對於初學者的我們來說碰到的衝突的概率基本等於零,就不用操心了。


把數據放入字典的方法,除了最開始的初始化,還可以指定key放入:

<code>>>> 成績單['趙六']=60
>>> 成績單
{'張三': 100, '李四': 90, '王五': 80, '趙六': 60}/<code>

一個key只能對應一個value,value值以最後一次被放入的為準,後面的值會把前面的值沖掉;如果查詢的key不在字典中就會直接報錯:

<code>>>> 成績單['李四']=60
>>> 成績單
{'張三': 100, '李四': 60, '王五': 80, '趙六': 60}
>>> 成績單['木人張']
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
成績單['木人張']
KeyError: '木人張'/<module>/<pyshell>/<code>

為了避免出現上面查詢的key不在字典內的情況,有兩種方法:1、用in判斷key是否在字典內:

<code>>>> '木人張' in 成績單
False/<code>

2、字典的get用法,如果key不存在就返回None,或者指定的內容(None是一個空值,在交互環境不會顯示結果):

<code>>>> 成績單.get('木人張',-1) 

-1
>>> 成績單.get('木人張')
>>> /<code>

要刪除一個key,用pop(key)方法,對應的value也會從dict中刪除:

<code>>>> 成績單.pop('張三')
100
>>> 成績單
{'李四': 60, '王五': 80, '趙六': 60}/<code>

和list比較,dict有以下幾個特點:查找和插入的速度極快,不會隨著key的增加而變慢;需要佔用大量的內存,內存浪費多。而list相反:查找和插入的時間隨著元素的增加而增加;佔用空間小,浪費內存很少。所以,dict是用空間來換取時間的一種方法。dict可以用在需要高速查找的很多地方,在Python代碼中幾乎無處不在,正確使用dict非常重要,需要牢記的第一條就是dict的key必須是不可變對象。要保證hash的正確性,作為key的對象就不能變。在Python中,字符串、整數等都是不可變的,因此,可以放心地作為key。而list是可變的,就不能作為key:

<code>>>> t=[1,2,3,4]
>>> d[t]= 'list'
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
d[t]= 'list'
NameError: name 'd' is not defined/<module>/<pyshell>/<code>

set

set和dict類似,是一組單純key的集合,但不存儲value。由於key不能重複,所以,在set中,沒有重複的key。要創建一個set,需要提供一個list作為輸入集合:

<code>>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}/<code>

注意,傳入的參數[1, 2, 3]是一個list,而顯示的{1, 2, 3}只是告訴你這個set內部有1,2,3這3個元素,顯示的順序也不表示set是有序的。。重複元素在set中自動被過濾:

<code>>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}/<code>

通過add(key)方法可以添加元素到set中,可以重複添加,但不會有效果:

<code>>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}/<code>

通過remove(key)方法可以刪除元素:

<code>>>> s.remove(4)
>>> s
{1, 2, 3}/<code>

set可以看成數學意義上的無序和無重複元素的集合,因此,兩個set可以做數學意義上的交集、並集等操作:

<code>>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}/<code>

set和dict的唯一區別僅在於沒有存儲對應的value,但是,set的原理和dict一樣,所以,同樣不可以放入可變對象,因為無法判斷兩個可變對象是否相等,也就無法保證set內部“不會有重複元素”。


分享到:


相關文章: