字典和集合

 

 

字典dict这种数据结构活跃在所有python程序的背后,即便你的程序没有直接使用它。

字典在python里的作用至关重要,python也对其实现做了高度优化,而背后的散列表则是字典性能出众的根本原因,集合可以看成是只含有键,没有值的特殊的字典。

泛映射类型

你一定听过linux里泛文件抽象的概念吧?即在linux中,一切皆文件。泛映射类型也类似,collections.abc模块中包含Mapping和MutableMapping这两个抽象基类,他们规定了泛映射类型需要满足的一些基本接口

我们一般不会直接继承这些抽象基本,而是根据具体需求对dict或者collections.UserDict进行扩展。

字典有多种构造方法。

1 a = dict(one=1, two=2, three=3)
2 b = {'one':1, 'two':2, 'three':3}
3 c = dict(zip(['one', 'two', 'three'], [1,2,3]))
4 d = dict([('one', 1), ('two', 2), ('three', 3)])

字典推导

有列表推导推广而来,可以方便的构建字典。

 1 dialCode = [
 2     (86, 'China'),
 3     (91, 'India'),
 4     (1, 'USA'),
 5     (7, 'Russia'),
 6     (81, 'Japan')
 7 ]
 8 
 9 d = {country: code for code, country in dialCode}
10 print(d)
11 print(d['USA'])
12 
13 d = {country: code for code, country in dialCode if code > 40}
14 print(d)

常见的映射类型方法

映射类型的方法很丰富。

摘自《流畅的python》

在以上的方法里,setdefault是比较微妙的一个。

考虑这样一个问题,在一段英文中查找每个单词出现的位置。

 1 import re
 2 words = "Mr. Johnson had never been up in an aerophane before and he had read a lot about air accidents, so one day when a friend offered to take him for a ride in his own small phane, Mr. Johnson was very worried about accepting. Finally, however, his friend persuaded him that it was very safe, and Mr. Johnson boarded the plane"
 3 
 4 result = {}
 5 word_re = re.compile(r'\w+')
 6 for word_match in word_re.finditer(words):
 7     word = word_match.group()
 8     start = word_match.start()
 9     end = word_match.end()
10     val = result.get(word, [])
11     val.append((start,end))
12     result[word] = val
13     
14 print(result)

这段代码没有问题,但是每找到一个单词就要对字典进行两次查找,一次get,一次__setitem__,尝试用setdefault方法解决这个问题。

setdefault接收一个key和一个default=None,当字典中存在key时,就返回key对应的value;否则就以(key, default)为键值对插入字典中,并返回default。

 1 result = {}
 2 word_re = re.compile(r'\w+')
 3 for word_match in word_re.finditer(words):
 4     word = word_match.group()
 5     start = word_match.start()
 6     end = word_match.end()
 7     #val = result.get(word, [])
 8     #val.append((start,end))
 9     #result[word] = val
10     result.setdefault(word, []).append((start, end))
11     
12 print(result)

可以看到,我们只用了一行代码就完成了查找key、更新key对应的值的功能。

映射的弹性键查询

有的时候,我们要查询的键就算在字典中不存在也希望可以得到一个默认值。有两个方法可以达到这个目的。

1、使用defaultdict

2、自定义一个dict或者UserDict的子类,自己实现__missing__方法

defaultdict

defaultdict位于collections模块中,需要给它提供一个可调用对象default_factory,当要查找的key通过__getitem__找不到时,就会调用该对象,产生一个默认值。

比如,dd = defaultdict(list), 如果'key'在dd中不存在的话,那么list方法被调用,('key', [])作为一对键值对,被插入字典中。

使用defaultdict完成上面setdefault同样的功能

 1 from collections import defaultdict
 2 result = defaultdict(list)
 3 word_re = re.compile(r'\w+')
 4 for word_match in word_re.finditer(words):
 5     word = word_match.group()
 6     start = word_match.start()
 7     end = word_match.end()
 8     result[word].append((start, end))
 9     
10 print(result)

Attention:可调用对象default_factory只会在__getitem__里被调用,其他方法不会有影响。result.get('1234',default=None)返回的依然是None,而不是[]。

特殊方法__missing__

顾名思义,__missing__方法在字典严格点说应该是映射类型找不到键的时候被调用,如果用户自定义了这个方法,那么在__getitem__找不到键的时候就会调用该方法,而不是抛出KeyError异常。

同样的,__missing__只会在__getitem__里被调用,对get或者__contains__(in)没有影响。

如果自定义映射类型,通常都会继承UserDict,而不是dict,主要是因为后者为了提升性能,对一些方法会进行优化,而我们可能就不得不重写这些方法了,UserDict不存在这个问题,而且UserDict不是dict的子类,它有一个dict对象保存在data属性里。

下面我们写一个支持字符串和数字查询、更新、添加的字典类型。

 1 from collections import UserDict
 2 class Mydict(UserDict):
 3     def __missing__(self, key):
 4         if isinstance(key, str): #思考为什么此处的判断是必须的呢?
 5             raise KeyError
 6         return self.data[str(key)]
 7 
 8     def __contains__(self, key):
 9         return str(key) in self.data
10 
11     def __setitem__(self, key, val):
12         self.data[str(key)] = val
13 mydict = Mydict()
14 mydict[1] = 'one'
15 mydict['2'] = 'two'
16 #print(1 in mydict, '1' in mydict)
17 print(mydict.get(1), mydict.get('1'))
18 print(mydict)

如果没有__missing__中的判断,key是字符串首先到__getitem__中查找不到,然后调用__missing__,__missing__又用self.data[str(key)]的方式又调用__getitem__,如此就会形成无限递归,最终栈溢出。

散列表

 

散列表其实就是一个稀疏矩阵,总是有空白元素的矩阵被称为稀疏矩阵。散列表里的单元又叫标远,每个表元包含两部分:对键的引用,对值的引用,表元的大小固定,因此通过偏移量来读取表元。

python会设法保证散列表中有大约1/3的表元是空的,所以在快要达到这个阈值的时候,python会把原来的散列表复制到一个更大的空间里。

一个元素要放入散列表,先要计算这个元素的散列值。hash()方法

1、散列值和相等性

  hash方法可以计算所有内置类型的散列值,如果是自定义类,需要实现__eq__和__hash__方法。两个对象如果在比较的时候是相等的,那么他们的散列值必须相等,否则散列表不能正常运行。散列值应该在散列表中尽量的分散开。

2、散列表算法

  dict[search_key],python首先会计算search_key对象的散列值,然后取较低的几位作为偏移量到散列表中查找表元,如果找到的表元是空的,就会抛出KeyError异常;否则,会拿到表元foundkey:found_value,然后比较foundkey和search是否相等,相等的话,就返回foundvalue,否则,就会发生散列冲突。

  散列冲突发生后,python会在散列值中另取几位,重复上述步骤,知道找到对应的value或者抛出异常为止。

 往字典里添加元素的时候,python会根据当前散列表里的具体情况决定是否要重新为散列表分配空间,这不是应用程序能控制的,作为程序开发者牢记一点:

  不要在迭代字典的过程中对字典添加新键!!

无论任何时候往字典里添加新键,python都有可能做出为散列表扩容的决定,因此散列表中原有键的次序可能会被打乱、以及发生散列冲突,导致的结果则是不可控的,很可能出现意料之外的情况。

集合的特点和字典几乎是完全一致的,这里做一点总结。

*  字典、集合里的元素必须是可散列的

*  字典、集合很消耗内存

*  字典、集合的存取效率很高

*  往字典、集合里添加元素,可能会改变散列表中原有元素的次序

转载于:https://www.cnblogs.com/forwardfeilds/p/10455234.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值