第3章 字典和集合
[toc]
字典推导式
可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的:
d = {key: value for (key, value) in iterable}
举个例子说明:
iterable = [(1, 'hello'), (2, 'world'), (3, 'happy')]
d = {key: value for (key, value) in iterable}
print(d) # {1: 'hello', 2: 'world', 3: 'happy'}
setdefault用法
使用setdefault处理找不到的键
简单例子:
d = {'a': 1, 'b': 2}
d['c'] = 3
# 若原来没有,设置,否则原值不变
d.setdefault('c', 4)
print(d) # {'a': 1, 'b': 2, 'c': 3}
字典也有get方法,如下所示:
d = {'a': 1, 'b': 2}
# d['c'] = 3
# 使用get 方法设置的默认值不会改变原字典
a = d.get('c', 4)
print(a) # 4
print(d) # {'a': 1, 'b': 2}
可以看到两者的区别:
get 方法设置的默认值不会改变原字典, 而setdefault设置的默认值会改变原字典的值。
再看一个例子:
some_dict = {'abc': 'a', 'cdf': 'b', 'gh': 'a', 'fh': 'g', 'hfz': 'g'}
new_dict = {}
for k, v in some_dict.items():
new_dict.setdefault(v, []).append(k)
print(new_dict) # {'a': ['abc', 'gh'], 'b': ['cdf'], 'g': ['fh', 'hfz']}
defaultdict用法
所有的映射类型在处理找不到的键的时候,都会牵扯到__missing__
方法。这也是这个方法称作“missing”的原因。虽然基类dict并没有定义这个方法,但是dict是知道有这么个东西存在的.
举个例子来说:
class Dict(dict):
def __missing__(self, key):
self[key] = []
return self[key]
dct = Dict()
print(dct["foo"]) # []
dct["foo"].append(1)
dct["foo"].append(2)
print(dct["foo"]) # [1,2]
defaultdict是属于collections 模块下的一个工厂函数,用于构建字典对象,
接收一个函数(可调用)对象为作为参数。
参数返回的类型是什么,key对应value就是什么类型
from collections import defaultdict
# 参数为 list,它就会构建一个默认value为list的字典,
# 例如result['a']的值默认就是list对象。
dct = defaultdict(list)
print(dct) # defaultdict(, {})
print(dct["a"]) # []
dct["a"].append("hello")
print(dct) # defaultdict(, {'a': ['hello']})
看一个例子:
from collections import defaultdict
result = defaultdict(list)
data = [("p", 1), ("p", 2), ("p", 3), ("h", 1), ("h", 2), ("h", 3)]
for (key, value) in data:
result[key].append(value)
print(result) # defaultdict(, {'p': [1, 2, 3], 'h': [1, 2, 3]})
print(dict(result)) # {'p': [1, 2, 3], 'h': [1, 2, 3]}
再看一个例子:
# 字典的统计
names = ['leo', 'sam', 'jack', 'peter', 'joe', 'susan']
# 要变成这样:{3: ['leo', 'sam', 'joe'], 4: ['jack'], 5: ['peter', 'susan']}
# bad
nums = [len(n) for n in names]
contain = {}
for k1, k2 in zip(nums, names):
print(k1, k2)
if k1 not in contain:
contain[k1] = [k2]
else:
contain[k1].append(k2)
print(contain)
print("#" * 10)
# better
from collections import defaultdict
d = defaultdict(list)
for name in names:
key = len(name)
d[key].append(name)
print(dict(d))
default性能效率检测
import timeit
from collections import defaultdict
def way1():
d = {}
chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000
for c in chars:
if c in d:
d[c] += 1
else:
d[c] = 1
return d
def way2():
d = defaultdict(int)
chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000
for c in chars:
d[c] += 1
return d
if __name__ == "__main__":
t1 = timeit.timeit("way1", setup="from __main__ import way1", number=10)
print(t1) # 6e-07(表示6x10^(-7),也就是6x0.0000001,如果写成6e07表示6x10^7)
t2 = timeit.timeit("way2", setup="from __main__ import way2", number=10)
print(t2) # 4.000000000000097e-07
对于字典的使用,我们要学会用 defaultdict来代替,
一来是因为有缺省值非常安全,如果访问不存在的key,不会报错;
二来是Pyhon性能会大幅提高。
仅仅换了字典数据结构,性能就大幅的提高了很多。
字典的变种
python字典 dict 默认是无序的,要有序请用collection中的orderdict
python 2 代码:
import collections
d = {}
d['name'] = 'zhf'
d['age'] = 33
d['city'] = 'chengdu'
for k, v in d.items():
print k, v
a = collections.OrderedDict()
a['name'] = 'zhf'
a['age'] = 33
a['city'] = 'chengdu'
print "*" * 10
for k, v in a.items():
print k,v
返回结果:
city chengdu
age 33
name zhf
**********
name zhf
age 33
city chengdu
python3.6.7 代码:
import collections
d = {}
d['name'] = 'zhf'
d['age'] = 33
d['city'] = 'chengdu'
for k, v in d.items():
print(k, v)
a = collections.OrderedDict()
a['name'] = 'zhf'
a['age'] = 33
a['city'] = 'chengdu'
print("*" * 10)
for k, v in a.items():
print(k, v)
运行结果:
name zhf
age 33
city chengdu
**********
name zhf
age 33
city chengdu
可以看到3.6版本的Python已经使得dict变得紧凑以及关键字变得有序
collections.Counter 用法
简单例子:
import collections
string = "aaabbbc"
ct = collections.Counter(string)
print(ct) # Counter({'a': 3, 'b': 3, 'c': 1})
print(dict(ct)) # {'a': 3, 'b': 3, 'c': 1}
ct.update('abcdef')
print(dict(ct)) # {'a': 4, 'b': 4, 'c': 2, 'd': 1, 'e': 1, 'f': 1}
集合
集合的本质是许多唯一对象的聚集。因此集合可以去掉重复的元素
d = ['abc', 'def', 'abc', 'def']
s = set(d)
print(s) # {'def', 'abc'}
假设有2个集合a和b,需要统计集合a的哪些元素在集合b中也出现。
如果不使用集合,代码只能写成下面的形式:
a = ['abc', 'def', 'aaa']
b = ['abc', 'bbb', 'ccc', 'def']
for n in a:
if n in b:
print(n)
但是如果使用集合,则不用这么麻烦。
在集合中。a|b返回的是合集,a&b返回的是交集。
a-b返回的是差集。-差集是指属于A但不属于B的结合
a = ['abc', 'def', 'aaa']
b = ['abc', 'bbb', 'ccc', 'def']
print(set(a) & set(b))
print(set(a) | set(b))
print(set(a) - set(b)) # 相等于 print(set(a).difference(b))
结果返回:
{'def', 'abc'}
{'aaa', 'def', 'ccc', 'abc', 'bbb'}
{'aaa'}
关于字典,列表,集合运行效率的对比
列表交集代码:
from time import time
t = time()
lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44]
listb = [2, 4, 6, 9, 23]
intersection = []
for i in range(1000000):
for a in lista:
for b in listb:
if a == b:
intersection.append(a)
print("total run time:%s" % (time() - t))
# total run time:5.015027046203613
集合交集代码:
from time import time
t = time()
lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44]
listb = [2, 4, 6, 9, 23]
intersection = []
for i in range(1000000):
list(set(lista) & set(listb))
print("total run time:%s" % (time() - t))
# total run time:1.0969467163085938
字典代码:
from time import time
t = time()
list = [
'a', 'b', 'is', 'python', 'jason', 'hello', 'hill', 'with', 'phone',
'test', 'dfdf', 'apple', 'pddf', 'ind', 'basic', 'none', 'baecr', 'var',
'bana', 'dd', 'wrd'
]
# list = dict.fromkeys(list, True) # total run time:0.9107456207275391
print(list)
filter = []
for i in range(1000000):
for find in ['is', 'hat', 'new', 'list', 'old', '.']:
if find not in list:
filter.append(find)
print("total run time:%s" % (time() - t))
# total run time:2.0197031497955322
Python 字典中使用了 hash table,因此查找操作的复杂度为 O(1),而 list 实际是个数组,在 list 中,查找需要遍历整个 list,其复杂度为 O(n),因此对成员的查找访问等操作字典要比 list 更快。
因此在需要多数据成员进行频繁的查找或者访问的时候,使用 dict 而不是 list 是一个较好的选择
一个良好的算法能够对性能起到关键作用,因此性能改进的首要点是对算法的改进。
在算法的时间复杂度排序上依次是:
O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)