python 基础知识梳理——字典和集合

python 基础知识梳理——字典和集合

字典和集合基础

字典是由键(key)和值(value)配对组成的元素的集合(在Python3.7之后字典有序成为了语言特性,因此3.6中无法保证其有序性),而Python3.6之前是无序的,其长度大小可变,元素可以任意地删减和改变。

相对于列表和元组,字典的性能更优,特别是对于查找、添加和删除操作,字典都能在常数时间复杂度内完成。

集合和字典基本相同,唯一的区别是 :集合没有键和值的配对,是一系列无序的、唯一的元素组合。

字典和集合的创建,通常有如下几种方式:

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

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

在集合和字典中,无论是键还是值,都可以是混合类型。

s = {
    
    1,'hello',5.0}

在元素访问时,可以采取索引访问,如果不存在就会抛出异常:

d = {
    
    'name':'json','age':20}
print(d['name'])
json
print(d['location'])
Traceback (most recent call last):
  File "/Users/gray/Desktop/test.py", line 4, in <module>
    print(d['location'])
KeyError: 'location'

也可以使用get(key,default)函数来进行索引。如果键不在,调用get()函数可以返回一个默认值。

d = {
    
    'name':'json','age':20}
d.get('name')
'json'
d.get('location','no data here!')
'no data here!'

在看集合之前,我们要强调:集合并不支持索引操作,因为集合的本质是一个哈希表,和列表不一样。

s = {
    
    1,2,3}
s[0]
Traceback (most recent call last):
  File "/Users/gray/Desktop/test.py", line 2, in <module>
    print(s[0])
TypeError: 'set' object is not subscriptable

想要判断一个元素在不在字典或集合内,我们可以使用value in dict/set 来判断

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

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

除了创建和访问,字典和集合也同样支持增加、删除、更新操作

d = {
    
    'name':'json','age':20}
d['gender'] = 'male' # 增加键值对'gender':'male'
d['dob'] = '1997-07-01' # 增加键值对'dob':'1997-07-01'
d
{
    
    'name': 'json', 'age': 20, 'gender': 'male', 'dob': '1997-07-01'}
d['dob'] = '1999-12-31' # 更新键值对的值
d.pop('dob')
'1999-12-31' 
d.pop('dob')# 删除'dob'键值对
d
{
    
    'name': 'json', '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()命令时会删除集合中最后一个元素,所以无法得知删除的是哪一个元素

在实际情况中,我们需要对字典和集合进行排序。对于字典,我们通常会根据键或值,进行升序或降序排序:

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

这里返回了一个列表,列表中的每个元素都是由原字典的键和值组成的元组。

对于集合,它的排序方法和之前博文中提到的列表、元组很类似,直接调用sorted(set)方法就会返回一个排好序的列表。

s = {
    
    4,3,2,1}
sorted(s)
{
    
    1,2,3,4}

注意:s.sort()方法会报错,此方法只适用于list列表

字典和集合性能

假设列表中有n个元素,而查找的过程要遍历列表,那么时间复杂度就是O(n),即使我们先对列表进行排序,然后使用二分查找,也会需要O(nlogn)的时间复杂度,更何况,列表排序还需要O(nlogn)的时间。

假如我们用字典来存储这些数据,那么查找的速度就会非常高效便捷。只需要O(1)的时间复杂度就可以完成,由于字典的内部实现是通过哈希表,可以通过直接键的哈希值,找到对应的值。

下面,我们通过初始化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))

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)

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)

# 计算列表处理时间
start_use_list = time.perf_counter()
find_unique_price_using_list(products)
end_use_list = time.perf_counter()
print('列表——处理时间:{}'.format(end_use_list-start_use_list))
# 计算集合处理时间
start_use_set = time.perf_counter()
find_unique_price_using_set(products)
end_use_set= time.perf_counter()
print('集合——处理时间:{}'.format(end_use_set-start_use_set))

print('集合的处理速度是列表的{}倍'.format((end_use_list-start_use_list)/(end_use_set-start_use_set)))

result:
列表——处理时间: 60.005643502000005(s)
集合——处理时间: 0.013591562000002(s)
集合的处理速度是列表的 4414.918866719747

处理的数据仅仅为100万条效率就相差4000多倍,当处理一亿条数据的时候,集合也只需要7秒,效率相对于列表,相当恐怖。

字典和集合的工作原理

要实现高效率的查找、删除、更新操作,和字典、集合的内部实现是分不开的,不同于其他的数据结构,字典和集合的内部结构是一张哈希表。

  • 对于字典而言,这张表存储了哈希值(hash)、键和值这3个元素
  • 对于集合来说,区别就是哈希表内没有 键和值配对,只有单一的元素了。

现在Python中的哈希表除了字典本身的结构,还会把索引值、键、值单独分开,就会出现下面这样的结构:

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-12-31'],
[9371539127, 'gender', 'male']
]

插入操作

每次向字典或集合插入一个元素的时候,Python会首先计算键的哈希值(hash(key)),再和mask=PyDicMinSize-1做与操作,计算这个元素插入哈希表的位置index=hash(key)&mask,如果哈希表中此位置是空的,那么这个元素就会被插入其中。

如果此时位置已经被占用,Python就会比较两个元素的哈希值和键是否相等。

  • 如果两者相等,则表明这个元素存在,如果值不同,则更新值
  • 如果两者中有一个不相等,则表示发生了哈希冲突,意思是两个元素的键不相等,但是哈希值相等。这种情况下,Python会继续寻找表中的空余位置,直到找到位置为止。

遇到这样的情况,一般来说会采用最简单的线性寻找,除此以外,还可以更换哈希算法或采用单链表法

查找操作

和插入操作类似,Python会根据哈希值,找到其该处于的位置;然后比较哈希表中这个位置中元素的哈希值和键,与需要查找的元素是否相等,如果相等,则直接返回;如果不等,则继续查找,直到找到空位或者抛出异常为止。

删除操作

对于删除操作,Python不会直接将它删除,而是暂时对它赋予一个特殊的值,等到重新调整哈希表的大小时,再将其删除。

哈希冲突会影响字典和集合的操作速度,因此,为了保证它们的高效性,字典和集合内的哈希表,通常会保证其至少留有1/3的剩余空间,随着元素的不断插入,当剩余空间少于1/3时,Python就会申请获取更大的内存空间,扩充哈希表,不过在这样的情况下,表内的所有元素都会被重新排序。

很明显的是,在哈希表中存储的数据越来越多时,哈希表中预留的1/3空间也会越来越大,导致哈希表越来越稀疏。






博文的后续更新,请关注我的个人博客:星尘博客

猜你喜欢

转载自blog.csdn.net/u011130655/article/details/113018900
今日推荐