Python 核心技术与实战 --02 字典和集合

什么是字典?
字典是一系列由键(key) 和 值(value) 配对组成的元素集合,在python3.7+ , 字典被确定为有序(注意在3.6 中,字典有序是一个implementation detail, 在3.7 才正式成为语言特性),而在3.6 无法100% 保证有序性,而在3.6 之前是无序的,其长度大小可变,元素可以任意地删减和改变。

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

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

首先我们来看字典和集合的创建,通常有以下几种方式

d1={‘name‘:‘jason‘,‘age‘:20}

d2=dict({‘name‘:‘jason‘,‘age‘:20})

d3=dict([(‘name‘,‘jason‘),(‘age‘,20)])

d4=dict(name=‘jason‘,age=20)

s1={1,2,3}

s2=set([1,2,3])

python 中字典和集合,无论是键还是值,都可以是混合类型。比如下面这个例子,我创建了一个元素为1,’hello‘,5.0 的集合

元素访问问题。字典访问可以直接索引键,如果不存在就抛出异常。

d ={‘name‘:jason‘,‘age‘:20}
d.get(‘name‘)
d.get(‘location‘,‘null‘)
如果不存在就返回 ‘null‘ 默认值

集合

集合本质上是一个哈希表,和列表不一样。所以集合不支持索引操作。

想要判断一个元素在不在字典或集合内,我们可以用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

增加/删除/更新操作

d={‘name‘:‘jason‘,‘age‘:20}
d[‘gender‘]=‘male‘
d[‘dob‘]=‘1999-02-01‘
d.pop(‘dob‘)

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

集合的pop() 操作是删除集合中最后一个元素,可是集合本身是无序的所以你无法知道会删除哪个元素,这个操作谨慎使用

实际应用中,很多情况下,我们需要对字典或集合进行排序,比如,取出值最大的50对。

对于字典,我们通常会根据键或者值,进行升序或降序排序:

d={"a‘:1,‘c‘:3,‘d‘:4}
sorted_by_key = sorted(d.items(),key=lambda x:x[0])
sorted_by_value = sorted(d.items(),key=lambda x:x[1])

字典和集合的性能

字典和集合是进行过高度性能优化的数据结构,特别是对于查找/添加/和删除操作。那接下来,我们就来看看,它们在具体的场景下的性能表现,以及与列表等其他数据结构进行对比

比如某电商企业的后台,存储了每件产品的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),
                (23121312,30),
                 (32421312,150)]

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

但如果使用字典来存储这些数据,那么查找就会非常便捷高效,只需要O(1) 的时间复杂度就可以完成。原因很简单,刚刚提到过的,字典的内部组成是一张哈希表,你可以直接通过键的哈希值,找到其对应的值。

类似的,现在需求变成,要找出这些商品有多少种不同的价格。我们还用同样的方法来比较一下。

如果还是选择用列表,对应的代码如下,其中,A和B是两层循环。同样假设原始列表有n个元素,那么最差的情况下,需要O(n^2)的时间复杂度。

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

但如果我们选择使用集合这个数据结构,由于集合是高度优化的哈希表,里面的元素不能重复,并且其添加和查找操作只需要O(1)的复杂度,那么,总的时间复杂度就只是O(n)

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)

字典和集合的工作原理:

字典集合内部的数据结构都是一张哈希表。

对于字典而言,这张表存储了哈希值(hash)/键/值这三个元素。

而对于集合来说,区别就是哈希表内没有键值的配对,只有单一的元素。

老版本的哈希表结构:

哈希值(hash)     键(key)          值(value)
---------------------------------------------
hash0                key0             value0
---------------------------------------------
hash1                key1             value1
---------------------------------------------
hash2                key2             value2
-------------------------------------------

不难想象,随着哈希表的扩张,它会变得越来越稀疏。举个例子,比如我有这样一个字典:

{‘name‘:‘mike‘,‘dob‘:‘1991-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
 

相关推荐