字典和集合的相關知識梳理

字典推導

列表推導和生成器表達式的概念就移植到了字典上,從而有了字典推導(後面還會看到集合推導)。
字典推導(dictcomp)可以從任何以鍵值對作爲元素的可迭代對象中構建出字典。

>>> DIAL_CODES = [                  #➊ 
...         (86, 'China'), 
...         (91, 'India'), 
...         (1, 'United States'), 
...         (62, 'Indonesia'), 
...         (55, 'Brazil'), 
...         (92, 'Pakistan'), 
...         (880, 'Bangladesh'), 
...         (234, 'Nigeria'), 
...         (7, 'Russia'), 
...         (81, 'Japan'), 
...     ] 
>>> country_code = {country: code for code, country in DIAL_CODES}  #➋ 
>>> country_code 
{'China': 86, 'India': 91, 'Bangladesh': 880, 'United States': 1,  
'Pakistan': 92, 'Japan': 81, 'Russia': 7, 'Brazil': 55, 'Nigeria':  
234, 'Indonesia': 62} 
>>> {code: country.upper() for country, code in country_code.items()  #➌ 
...   if code < 66} 
{1: 'UNITED STATES', 55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA'}

➊ 一個承載成對數據的列表,它可以直接用在字典的構造方法中。
➋ 這裏把配好對的數據左右換了下,國家名是鍵,區域碼是值。
➌ 跟上面相反,用區域碼作爲鍵,國家名稱轉換爲大寫,並且過濾掉區域碼大於或等於
66的地區。

集合論

集合的本質是許多唯一對象的聚集。因此,集合可以用於去重

>>> l = ['spam', 'spam', 'eggs', 'spam'] 
>>> set(l) 
{'eggs', 'spam'} 
>>> list(set(l)) 
['eggs', 'spam']

集合中的元素必須是可散列的,set類型本身是不可散列的,但是frozenset可以。因此
可以創建一個包含不同frozenset的set。

集合還實現了很多基礎的中綴運算符 。
給定兩個集合a和b,
a | b返回的是它們的合集,
a & b得到的是交集,
而a - b得到的是差集。

合理地利用這些操作,不僅能夠讓代碼的行數變少,還能減少Python程序的運行時間。

#needles的元素在haystack裏出現的次數,兩個變量都是set類型
found = len(needles & haystack)

如果不使用交集操作的話,代碼可能就變成了下面:

found = 0 
for n in needles: 
    if n in haystack: 
        found += 1

如果對象不是集合的話

#needles的元素在haystack裏出現的次數,這次的代碼可以用在任何可迭代對象上,,對對象沒有要求,可以隨時建立集合。
found = len(set(needles) & set(haystack)) 

集合字面量

除空集之外,集合的字面量——{1}、{1, 2},等等——看起來跟它的數學形式一模一樣。
如果是空集,那麼必須寫成set()的形式

如果要創建一個空集,你必須用不帶任何參數的構造方法set()。如果只是寫成{}的形式,跟以前一樣,你創建的其實是個空字典。

除了空集,集合的字符串表示形式總是以{…}的形式出現。

>>> s = {1} 
>>> type(s) 
<class 'set'> 
>>> s 
{1} 
>>> s.pop() 
1 
>>> s 
set()

像{1, 2, 3}這種字面量句法相比於構造方法(set([1, 2, 3]))要更快且更易讀

集合推導

#新建一個Latin-1字符集合,該集合裏的每個字符的Unicode名字裏都有“SIGN”這個單詞
>>>from unicodedata import name  #➊
>>>{char(i) for i in range(32,256) if 'SIGN'in name(char(i),'')}  #➋ 
{'§', '=', '¢', '#', '¤', '<', '¥', 'μ', '×', '$', '¶', '£', '©',  
'°', '+', '÷', '±', '>', '¬', '®', '%'}

➊ 從unicodedata模塊裏導入name函數,用以獲取字符的名字。
➋ 把編碼在32~255之間的字符的名字裏有“SIGN”單詞的挑出來,放到一個集合裏。
跟句法相關的內容就講到這裏,下面看看用於集合類型的豐富操作。

集合的操作

集合的數學運算:這些方法或者會生成新集合,或者會在條件允許的情況下就地修改集合
在這裏插入圖片描述
集合的 較運算符,返回值是布爾類型
在這裏插入圖片描述
集合類型的其他方法
在這裏插入圖片描述

dict和set的背後

字典中的散列表

散列表其實是一個稀疏數組(總是有空白元素的數組稱爲稀疏數組)。在一般的數據結構教材中,散列表裏的單元通常叫作表元(bucket)。在dict的散列表當中,每個鍵值對都佔用一個表元,每個表元都有兩個部分,一個是對鍵的引用,另一個是對值的引用。因爲所有表元的大小一致,所以可以通過偏移量來讀取某個表元。

如果要把一個對象放入散列表,那麼首先要計算這個元素鍵的散列值。Python中可以用hash()方法來計算散列值

內置的hash()方法可以用於所有的內置類型對象。如果是自定義對象調用hash()的話,
實際上運行的是自定義的__hash__。如果兩個對象在比較的時候是相等的,那它們的散列值必須相等,否則散列表就不能正常運行了。

如果一個對象的哈希值在其生存期內從未改變(它需要一個hash方法),並且可以與其他對象進行比較(它需要一個hash方法),那麼該對象是散列的。

比較相等的哈希對象必須具有相同的哈希值。

散列性使對象可用作字典鍵和集合成員,因爲這些數據結構在內部使用散列值。

Python的大多數不可變內置對象都是散列的;不可變容器(如列表或字典)不是; 不可變容器(如元組和frozenset)只有在其元素是散列的情況下才是散列的。默認情況下,作爲用戶定義類實例的對象是可散列的。 它們都比較不相等(除了它們自己),它們的散列值是從它們的id()派生的。

>>>tt = (1,2,(30,40))
>>>hash(tt)
8027212646858338501
>>>t1 = (1,2,frozenset([30,40]))
>>>hash(t1)
985328935373711578

散列衝突

從字典中取值的算法流程圖;給定一個鍵,這個算法要麼返回一個值,要麼拋出KeyError異常
在這裏插入圖片描述

dict的實現及其導致的結果

1. 鍵必須是可散列的
一個可散列的對象必須滿足以下要求。
(1) 支持hash()函數,並且通過__hash__()方法所得到的散列值是不變的。
(2) 支持通過__eq__()方法來檢測相等性。
(3) 若a == b爲真,則hash(a) == hash(b)也爲真。
所有由用戶自定義的對象默認都是可散列的,因爲它們的散列值由id()來獲取,而且它們
都是不相等的。

2. 字典在內存上的開銷巨大
由於字典使用了散列表,而散列表又必須是稀疏的,這導致它在空間上的效率低下。

3. 鍵查詢很快
dict的實現是典型的空間換時間:字典類型有着巨大的內存開銷,但它們提供了無視數據
量大小的快速訪問——只要字典能被裝在內存裏。

4. 鍵的次序取決於添加順序

當往dict裏添加新鍵而又發生散列衝突的時候,新鍵可能會被安排存放到另一個位置。
於是下面這種情況就會發生:由dict([key1, value1), (key2, value2)]和dict([key2, value2], [key1, value1])得到的兩個字典,在進行比較的時候,它們是相等的;但是如果在key1和key2被添加到字典裏的過程中有衝突發生的話,這兩個鍵出現在字典裏的順序是不一樣的。

#將同樣的數據以不同的順序添加到3個字典裏
# 世界人口數量前10位國家的電話區號 
DIAL_CODES = [ 
        (86, 'China'), 
        (91, 'India'), 
        (1, 'United States'), 
        (62, 'Indonesia'), 
        (55, 'Brazil'), 
        (92, 'Pakistan'), 
        (880, 'Bangladesh'), 
        (234, 'Nigeria'), 
        (7, 'Russia'), 
        (81, 'Japan'), 
    ] 
 
d1 = dict(DIAL_CODES)  #➊ 
print('d1:', d1.keys()) 
d2 = dict(sorted(DIAL_CODES))  #➋ 
print('d2:', d2.keys()) 
d3 = dict(sorted(DIAL_CODES, key=lambda x:x[1]))  #➌ 
print('d3:', d3.keys()) 
assert d1 == d2 and d2 == d3  #➍

➊ 創建d1的時候,數據元組的順序是按照國家的人口排名來決定的。
➋ 創建d2的時候,數據元組的順序是按照國家的電話區號來決定的。
➌ 創建d3的時候,數據元組的順序是按照國家名字的英文拼寫來決定的。
➍ 這些字典是相等的,因爲它們所包含的數據是一樣的。

3個字典的鍵的順序是不一樣的

d1: dict_keys([880, 1, 86, 55, 7, 234, 91, 92, 62, 81]) 
d2: dict_keys([880, 1, 91, 86, 81, 55, 234, 7, 92, 62]) 
d3: dict_keys([880, 81, 1, 86, 55, 7, 234, 91, 92, 62])

5. 往字典裏添加新鍵可能會改變已有鍵的順序
無論何時往字典裏添加新的鍵,Python解釋器都可能做出爲字典擴容的決定。擴容導致的結果就是要新建一個更大的散列表,並把字典裏已有的元素添加到新表裏。這個過程中可能會發生新的散列衝突,導致新散列表中鍵的次序變化。

set的實現以及導致的結果

set和frozenset的實現也依賴散列表,但在它們的散列表裏存放的只有元素的引用(就像在字典裏只存放鍵而沒有相應的值)。

• 集合裏的元素必須是可散列的。
• 集合很消耗內存。
• 可以很高效地判斷元素是否存在於某個集合。
• 元素的次序取決於被添加到集合裏的次序。
• 往集合裏添加元素,可能會改變集合裏已有元素的次序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章