日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

【学习笔记】第五章 python3核心技术与实践--字典和集合

發布時間:2024/4/14 python 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【学习笔记】第五章 python3核心技术与实践--字典和集合 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【第四章】思考題的答案,僅供參考:

[]比list()更快,因為調用了list函數有一定的時間,而[]卻沒有。

?

  前面我們學習了 Python 中的列表和元組,了解了他們的基本操作和性能比較。這節章,我們再來學習兩個同樣很常見并且很有用的數據結構:

字典(dict)和集合(set)

字典和集合在 Python 被廣泛使用,并且性能進行了高度優化,其重要性不言而喻。

一、字典和集合基礎

字典是一系列由鍵(key)和值(value)配對組成的元素的集合,在 Python3.7+,字典被確定為有序(注意:在 3.6 中,字典有序是一個 implementation detail,在 3.7 才正式成為語言特性,因此 3.6 中無法 100% 確保其有序性),而 3.6 之前是無序的,其長度大小可變,元素可以任意地刪減和改變。

相比于列表和元組,字典的性能更優,特別是對于查找、添加和刪除操作,字典都能在常數時間復雜度內完成。

集合和字典基本相同,唯一的區別,就是集合沒有鍵和值的配對,是一系列無序的、唯一的元素組合。

首先我們來看字典和集合的創建,通常有下面這幾種方式:

d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])
d4 = dict(name='jason', age=20, gender='male')
d1 == d2 == d3 ==d4
True

s1 = {1, 2, 3}
s2 = set([1, 2, 3])
s1 == s2
True

這里注意,Python 中字典和集合,無論是鍵還是值,都可以是混合類型。比如下面這個例子,我創建了一個元素為1,'hello',5.0的集合:

s = {1, 'hello', 5.0}

1、字典:元素的訪問

字典訪問可以直接索引鍵,如果不存在,就會拋出異常:

d = {'name': 'jason', 'age': 20}
d['name']
'jason'
d['location']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'location'

也可以使用 get(key, default) 函數來進行索引。如果鍵不存在,調用 get() 函數可以返回一個默認值。比如下面這個示例,返回了'null'。

d = {'name': 'jason', 'age': 20}
d.get('name')
'jason'
d.get('location', 'null')
'null'

2、集合:元素的訪問

首先我要強調的是,集合并不支持索引操作,因為集合本質上是一個哈希表,和列表不一樣。所以,下面這樣的操作是錯誤的,Python 會拋出異常:

s = {1, 2, 3}
s[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'set' object does not support indexing

想要判斷一個元素在不在字典或集合內,我們可以用 value in dict/set 來判斷。

s = {1, 2, 3}
1 in s
True
10 in s
False

d = {'name': 'jason', 'age': 20}
'name' in d
True
'location' in d
False

3、字典和集合也同樣支持增加、刪除、更新等操作

d = {'name': 'jason', 'age': 20}
d['gender'] = 'male' # 增加元素對'gender': 'male'
d['dob'] = '1999-02-01' # 增加元素對'dob': '1999-02-01'
d
{'name': 'jason', 'age': 20, 'gender': 'male', 'dob': '1999-02-01'}
d['dob'] = '1998-01-01' # 更新鍵'dob'對應的值
d.pop('dob') # 刪除鍵為'dob'的元素對
'1998-01-01'
d
{'name': 'jason', 'age': 20, 'gender': 'male'}

s = {1, 2, 3}
s.add(4) # 增加元素 4 到集合
s
{1, 2, 3, 4}
s.remove(4) # 從集合中刪除元素 4
s
{1, 2, 3}

不過要注意,集合的 pop() 操作是刪除集合中最后一個元素,可是集合本身是無序的,你無法知道會刪除哪個元素,因此這個操作得謹慎使用。

實際應用中,很多情況下,我們需要對字典或集合進行排序,比如,取出值最大的 50 對。

對于字典,我們通常會根據鍵或值,進行升序或降序排序:

d = {'b': 1, 'a': 2, 'c': 10}
d_sorted_by_key = sorted(d.items(), key=lambda x: x[0]) # 根據字典鍵的升序排序
d_sorted_by_value = sorted(d.items(), key=lambda x: x[1]) # 根據字典值的升序排序
d_sorted_by_key
[('a', 2), ('b', 1), ('c', 10)]
d_sorted_by_value
[('b', 1), ('a', 2), ('c', 10)]

這里返回了一個列表。列表中的每個元素,是由原字典的鍵和值組成的元組。

而對于集合,其排序和前面講過的列表、元組很類似,直接調用 sorted(set) 即可,結果會返回一個排好序的列表。

s = {3, 4, 2, 1}
sorted(s) # 對集合的元素進行升序排序
[1, 2, 3, 4]

二、字典和集合性能

文章開頭我就說到了,字典和集合是進行過性能高度優化的數據結構,特別是對于查找、添加和刪除操作。那接下來,我們就來看看,它們在具體場景下的性能表現,以及與列表等其他數據結構的對比。

比如電商企業的后臺,存儲了每件產品的 ID、名稱和價格。現在的需求是,給定某件商品的 ID,我們要找出其價格。

如果我們用列表來存儲這些數據結構,并進行查找,相應的代碼如下:

def find_product_price(products, product_id):
for id, price in products:
if id == product_id:
return price
return None

products = [
(143121312, 100),
(432314553, 30),
(32421912367, 150)
]

print('The price of product 432314553 is {}'.format(find_product_price(products, 432314553)))

# 輸出
The price of product 432314553 is 30

假設列表有 n 個元素,而查找的過程要遍歷列表,那么時間復雜度就為 O(n)。即使我們先對列表進行排序,然后使用二分查找,也會需要 O(logn) 的時間復雜度,更何況,列表的排序還需要 O(nlogn) 的時間。

但如果我們用字典來存儲這些數據,那么查找就會非常便捷高效,只需 O(1) 的時間復雜度就可以完成。原因也很簡單,剛剛提到過的,字典的內部組成是一張哈希表,你可以直接通過鍵的哈希值,找到其對應的值。

products = {
143121312: 100,
432314553: 30,
32421912367: 150
}
print('The price of product 432314553 is {}'.format(products[432314553]))

# 輸出
The price of product 432314553 is 30

類似的,現在需求變成,要找出這些商品有多少種不同的價格。我們還用同樣的方法來比較一下。

如果還是選擇使用列表,對應的代碼如下,其中,A 和 B 是兩層循環。同樣假設原始列表有 n 個元素,那么,在最差情況下,需要 O(n^2) 的時間復雜度。

# list version
def find_unique_price_using_list(products):
unique_price_list = []
for _, price in products: # A
if price not in unique_price_list: #B
unique_price_list.append(price)
return len(unique_price_list)

products = [
(143121312, 100),
(432314553, 30),
(32421912367, 150),
(937153201, 30)
]
print('number of unique price is: {}'.format(find_unique_price_using_list(products)))

# 輸出
number of unique price is: 3

但如果我們選擇使用集合這個數據結構,由于集合是高度優化的哈希表,里面元素不能重復,并且其添加和查找操作只需 O(1) 的復雜度,那么,總的時間復雜度就只有 O(n)。

# set version
def find_unique_price_using_set(products):
unique_price_set = set()
for _, price in products:
unique_price_set.add(price)
return len(unique_price_set)

products = [
(143121312, 100),
(432314553, 30),
(32421912367, 150),
(937153201, 30)
]
print('number of unique price is: {}'.format(find_unique_price_using_set(products)))

# 輸出
number of unique price is: 3

可能你對這些時間復雜度沒有直觀的認識,我可以舉一個實際工作場景中的例子,讓你來感受一下。

下面的代碼,初始化了含有 100,000 個元素的產品,并分別計算了使用列表和集合來統計產品價格數量的運行時間:

import time
id = [x for x in range(0, 100000)]
price = [x for x in range(200000, 300000)]
products = list(zip(id, price))

# 計算列表版本的時間
start_using_list = time.perf_counter()
find_unique_price_using_list(products)
end_using_list = time.perf_counter()
print("time elapse using list: {}".format(end_using_list - start_using_list))
## 輸出
time elapse using list: 41.61519479751587

# 計算集合版本的時間
start_using_set = time.perf_counter()
find_unique_price_using_set(products)
end_using_set = time.perf_counter()
print("time elapse using set: {}".format(end_using_set - start_using_set))
# 輸出
time elapse using set: 0.008238077163696289

你可以看到,僅僅十萬的數據量,兩者的速度差異就如此之大。事實上,大型企業的后臺數據往往有上億乃至十億數量級,如果使用了不合適的數據結構,就很容易造成服務器的崩潰,不但影響用戶體驗,并且會給公司帶來巨大的財產損失。

三、字典和集合的工作原理

我們通過舉例以及與列表的對比,看到了字典和集合操作的高效性。不過,字典和集合為什么能夠如此高效,特別是查找、插入和刪除操作?

這當然和字典、集合內部的數據結構密不可分。不同于其他數據結構,字典和集合的內部結構都是一張哈希表。

對于字典而言,這張表存儲了哈希值(hash)、鍵和值這 3 個元素。
而對集合來說,區別就是哈希表內沒有鍵和值的配對,只有單一的元素了。
我們來看,老版本 Python 的哈希表結構如下所示:

--+-------------------------------+
| 哈希值 (hash) 鍵 (key) 值 (value)
--+-------------------------------+
0 | hash0 key0 value0
--+-------------------------------+
1 | hash1 key1 value1
--+-------------------------------+
2 | hash2 key2 value2
--+-------------------------------+
. | ...
__+_______________________________+

不難想象,隨著哈希表的擴張,它會變得越來越稀疏。舉個例子,比如我有這樣一個字典:

{'name': 'mike', 'dob': '1999-01-01', 'gender': 'male'}

那么它會存儲為類似下面的形式:

entries = [
['--', '--', '--']
[-230273521, 'dob', '1999-01-01'],
['--', '--', '--'],
['--', '--', '--'],
[1231236123, 'name', 'mike'],
['--', '--', '--'],
[9371539127, 'gender', 'male']
]

這樣的設計結構顯然非常浪費存儲空間。為了提高存儲空間的利用率,現在的哈希表除了字典本身的結構,會把索引和哈希值、鍵、值單獨分開,也就是下面這樣新的結構:

Indices
----------------------------------------------------
None | index | None | None | index | None | index ...
----------------------------------------------------

Entries
--------------------
hash0 key0 value0
---------------------
hash1 key1 value1
---------------------
hash2 key2 value2
---------------------
...
---------------------

那么,剛剛的這個例子,在新的哈希表結構下的存儲形式,就會變成下面這樣:

indices = [None, 1, None, None, 0, None, 2]
entries = [
[1231236123, 'name', 'mike'],
[-230273521, 'dob', '1999-01-01'],
[9371539127, 'gender', 'male']
]

我們可以很清晰地看到,空間利用率得到很大的提高。

清楚了具體的設計結構,我們接著來看這幾個操作的工作原理。

四、插入操作

每次向字典或集合插入一個元素時,Python 會首先計算鍵的哈希值(hash(key)),再和 mask = PyDicMinSize - 1 做與操作,計算這個元素應該插入哈希表的位置 index = hash(key) & mask。如果哈希表中此位置是空的,那么這個元素就會被插入其中。

而如果此位置已被占用,Python 便會比較兩個元素的哈希值和鍵是否相等。

若兩者都相等,則表明這個元素已經存在,如果值不同,則更新值。
若兩者中有一個不相等,這種情況我們通常稱為哈希沖突(hash collision),意思是兩個元素的鍵不相等,但是哈希值相等。這種情況下,Python 便會繼續尋找表中空余的位置,直到找到位置為止。
值得一提的是,通常來說,遇到這種情況,最簡單的方式是線性尋找,即從這個位置開始,挨個往后尋找空位。當然,Python 內部對此進行了優化(這一點無需深入了解,你有興趣可以查看源碼,我就不再贅述),讓這個步驟更加高效。

五、查找操作

和前面的插入操作類似,Python 會根據哈希值,找到其應該處于的位置;然后,比較哈希表這個位置中元素的哈希值和鍵,與需要查找的元素是否相等。如果相等,則直接返回;如果不等,則繼續查找,直到找到空位或者拋出異常為止。

六、刪除操作

對于刪除操作,Python 會暫時對這個位置的元素,賦于一個特殊的值,等到重新調整哈希表的大小時,再將其刪除。

不難理解,哈希沖突的發生,往往會降低字典和集合操作的速度。因此,為了保證其高效性,字典和集合內的哈希表,通常會保證其至少留有 1/3 的剩余空間。隨著元素的不停插入,當剩余空間小于 1/3 時,Python 會重新獲取更大的內存空間,擴充哈希表。不過,這種情況下,表內所有的元素位置都會被重新排放。

雖然哈希沖突和哈希表大小的調整,都會導致速度減緩,但是這種情況發生的次數極少。所以,平均情況下,這仍能保證插入、查找和刪除的時間復雜度為 O(1)。

七、總結

我們一起學習了字典和集合的基本操作,并對它們的高性能和內部存儲結構進行了講解。

字典在 Python3.7+ 是有序的數據結構,而集合是無序的,其內部的哈希表存儲結構,保證了其查找、插入、刪除操作的高效性。所以,字典和集合通常運用在對元素的高效查找、去重等場景。

八、思考題

1. 下面初始化字典的方式,哪一種更高效?

# Option A
d = {'name': 'jason', 'age': 20, 'gender': 'male'}

# Option B
d = dict({'name': 'jason', 'age': 20, 'gender': 'male'})

2. 字典的鍵可以是一個列表嗎?下面這段代碼中,字典的初始化是否正確呢?如果不正確,可以說出你的原因嗎?

d = {'name': 'jason', ['education']: ['Tsinghua University', 'Stanford University']}

思考題答案見下章

轉載于:https://www.cnblogs.com/tianyu2018/p/10964920.html

總結

以上是生活随笔為你收集整理的【学习笔记】第五章 python3核心技术与实践--字典和集合的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。