理解Python字典和集合的性能差異


本文為極客時間 Python核心技術與實戰 專欄的學習筆記

字典

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

相比列表和元組,字典性能更優,可以在常數時間復雜度O(1)內完成查找、添加、刪除操作。

常用創建方法

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

索引

使用dict[key]格式索引,如果不存在,會拋出KeyError異常。

>>> d1['name']
'Json'
>>> d1['location']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'location'

使用get(key, default)方法不會拋出異常,此外當該鍵不存在時,可以指定返回的默認值

>>> d1.get('name')
'Json'
>>> d1.get('location')

>>> d1.get('location', None)
None

判斷某元素是否是字典的鍵:

>>> 'name' in d1		# 同d1.keys()等價
True
>>> 'name' in d1.keys()		
True
>>> 'Json' in d1		
False
>>> 'Json' in d1.values()
True

添加

>>> d = {'name': 'jason', 'age': 20}
>>> d['gender'] = 'male'
>>> d
{'name': 'jason', 'age': 20, 'gender': 'male'}

刪除

>>> d.pop('gender')
'male'
>>> d
{'name': 'jason', 'age': 20}

排序

根據鍵排序

>>> d = {'b': 1, 'v': 20, 'a': 17}
>>> d_sorted_by_key = sorted(d.items(), key=lambda x: x[0])
>>> d_sorted_by_key
[('a', 17), ('b', 1), ('v', 20)]
>>> d
{'b': 1, 'v': 20, 'a': 17}

根據值排序

>>> d_sorted_by_value = sorted(d.items(), key = lambda x: x[1])
>>> d_sorted_by_value
[('b', 1), ('a', 17), ('v', 20)]

性能分析

舉例:有1000萬件產品,產品信息包括:產品ID、價格。現在需求是:給定某件產品的ID,找出其價格:

1.用列表來存儲數據

存儲結構如下:

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

那么查找需要遍歷整個列表,時間復雜度為O(n)。即使先對列表排序,然后二分查找,也會需要O(logn)的時間復雜度,並且排序還需要O(nlogn)的時間。

2. 用字典存儲數據

存儲結構如下:

products = {
	'143121312': 100,
	'432314553': 30,
	'32421912367': 150
}

因為字典內部結構是一張哈希表,所以可以在O(1)的時間復雜度內完成查找。

3. 效率對比

import time
import numpy
def find_product_price_list(products, product_id):
    for id, price in products:
        if id == product_id:
            return price
    return None
def find_product_price_dict(products, product_id):
    for id in products.keys():
        if id == product_id:
            return products_dict[id]
    return None
r = numpy.random.randint(0,10000000,10000000)       # 生成10000000個隨機數
id = [str(x) for x in r]
price = [x for x in range(20000000, 30000000)]

products_list = list(zip(id, price))
products_dict = dict(zip(id, price))
# 添加新元素
products_list.append(('111111111', 300))	# 追加到列表末尾
products_dict['111111111'] = 300

start_using_dict = time.perf_counter()
find_product_price_dict(products_dict, '111111111')
end_using_dict = time.perf_counter()
print('time elapse using dict: {}'.format(end_using_dict - start_using_dict))

start_using_list = time.perf_counter()
find_product_price_list(products_dict, '111111111')
end_using_list = time.perf_counter()

print('time elapse using list: {}'.format(end_using_list - start_using_list))

# ===========運行結果============
time elapse using dict: 0.1983588489999999
time elapse using list: 0.41368435999999953

集合

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

常用創建方法

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

索引

集合並不支持索引操作,因為集合本質上是一個哈希表,和列表不一樣

進行如下操作,Python會拋出TypeError異常。

>>> s1[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'set' object is not subscriptable

只能判斷某元素是否在集合中:

>>> 1 in s1
True
>>> 10 in s1
False

添加

>>> s = {1,2,3}
>>> s.add(4)
>>> s
{1, 2, 3, 4}

刪除

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

注意:由於集合是無序的,所以無法確定pop()方法會刪除哪個元素,所以謹慎使用。一般刪除操作采用remove()即可。

排序

>>> s  = {2,4,546,34}
>>> sorted(s)
[2, 4, 34, 546]

集合運算

常用集合運算

語法 操作 說明
set(list1) | set(list2) union 包含 list1 和 list2 所有數據的新集合
set(list1) & set(list2) intersection 包含 list1 和 list2 中共同元素的新集合
set(list1) - set(list2) difference 在 list1 中出現但不在 list2 中出現的元素的集合

性能分析

還是以上面那個例子為例,現在要求計算出有多少種價格。為了節省時間,我們把產品數量降低到10萬。

查找效率

1. 用列表存儲數據

需要兩層循環。那么,在最差情況下,需要 O(n^2) 的時間復雜度。

2. 用集合存儲數據

由於集合是高度優化的哈希表,里面元素不能重復,並且其添加和查找操作只需 O(1) 的復雜度,那么,總的時間復雜度就只有 O(n)

3. 效率對比

import time
import numpy

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

def find_unique_price_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)


r = numpy.random.randint(0,1000000,100000)       # 生成100000個隨機數
id = [str(x) for x in r]
price = [x for x in range(200000, 300000)]

products = list(zip(id, price))


start_using_set = time.perf_counter()
find_unique_price_set(products)
end_using_set = time.perf_counter()
print('time elapse using set: {}'.format(end_using_set - start_using_set))

start_using_list = time.perf_counter()
find_unique_price_list(products)
end_using_list = time.perf_counter()
print('time elapse using list: {}'.format(end_using_list - start_using_list))

# ===========運行結果============
time elapse using set: 0.00985934799999999
time elapse using list: 65.528253501

可以看出,僅10萬數據,差距就已經很明顯了。

交集、並集、差集運算

以求交集為例:

import time

list_a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44]
list_b = [2, 4, 6, 9, 23]

intersection = []

# 列表做交集
start_using_list = time.perf_counter()
for a in list_a:
    for b in list_b:
        if a == b:
            intersection.append(a)
end_using_list = time.perf_counter()

print(intersection)
print('time: {}'.format(end_using_list - start_using_list))

# 集合做交集
start_using_list = time.perf_counter()
intersection = list(set(list_a) & set(list_b))
end_using_list = time.perf_counter()

print(intersection)
print('time: {}'.format(end_using_list - start_using_list))

# ===========運行結果============
[2, 4, 6, 9]
time: 9.622000000000797e-06
[9, 2, 4, 6]
time: 4.169000000001782e-06


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM