collections简介
python提供了4种基本的数据结构:list、tuple、dict、set。基本数据结构完全可以hold住所有的场景,但是在处理数据结构复杂的场景时,这4种数据结构有时会显的单一,比如将相同字母组成的字符串归类到列表中,是一个key为字符串,value为列表的数据结构,复杂度为O(1)的情况下完成LRU(力扣原题)。
这个时候今天的主角collections
包就可以登场了。collections是基本数据结构的高性能优化版,它提供了多个有用的集合类,熟练掌握这些集合类,不仅可以让我们让写出的代码更加Pythonic,也可以提高我们程序的运行效率。
Counter
简单使用
Counter是一个简单的计数器,例如,统计字符出现的个数,并且会根据出现次数从大到小排列好:
>>> from collections import Counter
>>> Counter("hello world")
Counter({'l': 3, 'o': 2, ' ': 1, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
>>>
功能:
Counter是一个计数器工具,提供快速和方便的计数。从类型上划分,Counter是一个dict的子类,元素像字典一样存储,key是统计的元素,value是统计的个数。
Counter可统计多种数据类型,包括:字符串,列表,字典,元组,集合等。
字符串
>>> str = "hello world"
>>>
>>> res = Counter(str)
>>> res
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
列表
>>> arr = [1,2,3,4,2,3,4,5]
>>> res = Counter(arr)
>>> res
Counter({2: 2, 3: 2, 4: 2, 1: 1, 5: 1})
字典
>>> map = {"a":3, "b":2, "c":1}
>>> res = Counter(map)
>>> res
Counter({'a': 3, 'b': 2, 'c': 1})
元组
>>> tu = (1,2,3,2,3,4,2,3,5)
>>> res = Counter(tu)
>>> res
Counter({2: 3, 3: 3, 1: 1, 4: 1, 5: 1})
集合
>>> Set = {1,2,3,3,4,5,4,5,6}
>>> Set
{1, 2, 3, 4, 5, 6}
>>> res = Counter(Set)
>>> res
Counter({1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1})
Counter拥有的方法
1.elements()
返回一个迭代器,其中每个元素将重复出现计数值所指定次。元素返回的顺序是按照元素在原本序列中首次出现的顺序
>>> str = "hello world"
>>> res = Counter(str)
>>> res
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
>>> list(res.elements())
['h', 'e', 'l', 'l', 'l', 'o', 'o', ' ', 'w', 'r', 'd']
>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> list(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
2.most_common()
返回一个列表,其中包含n个出现次数最高的元素及出现次数,按常见程度由高到低排序。 如果 n 被省略或为None, 将返回计数器中的所有元素。
计数值相等的元素按首次出现的顺序排序,经常用来计算top词频的词语。
>>> str = "hello world"
>>> res = Counter(str)
>>> res
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
>>> res.most_common(2)
[('l', 3), ('o', 2)]
3.subtract()
将两个Counter相减,a.sbutract(b),a中计数减少相应的个数
>>> from collections import Counter
>>> a = Counter("hello world")
>>> a
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
>>> b = Counter("hello")
>>> a.subtract(b)
>>> a
Counter({'l': 1, 'o': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, 'h': 0, 'e': 0})
4.字典方法
通常字典方法都可用于Counter对象,除了有两个方法工作方式与字典并不相同。
a. fromkeys(iterable)
这个类方法没有在Counter中实现。
b. update([iterable-or-mapping])
Counter对象更新另一个对象。但是不同之处在于字典的update是更新元素的value,而Counter的update是将元素的统计相加。
defaultdict
defaultdict是带默认值的字典,属于内置字典的子集,它重写了一个方法和添加一个可写实例变量。普通字典在取出一个不存在的key时,会抛出KeyError错误。如果希望在key不存在时返回一个默认值,可以使用字典的get方法,如map.get(key, None),或者是使用defaultdict。defaultdict作用就是在字典查找不存在的key时返回一个默认值而不是KeyError。
功能:
为添加到字典中的每一个key增加一个默认值,当查询一个不存在的元素时,会返回该默认类型和添加一个可写实例变量。剩下的方法都和常规字典一样。
默认值为列表
>>> dict_demo = defaultdict(list)
>>> arr = [("yello", 1),("blue", 2), ("green",3), ("yello", 9)]
>>> for key, value in arr:
... dict_demo[key].append(value)
...
>>> dict_demo
defaultdict(<class 'list'>, {'yello': [1, 9], 'blue': [2], 'green': [3]})
默认值为数字
>>> from collections import defaultdict
>>> dict_demo = defaultdict(int)
>>> count = [("a",1),("b",2),("c",3),("a",4)]
>>>
>>> for key, value in count:
... dict_demo[key] += value
...
>>> dict_demo
defaultdict(<class 'int'>, {'a': 5, 'b': 2, 'c': 3})
默认值的类型
dict_demo = defaultdict(str) 默认值:空字符串
dict_demo = defaultdict(int) 默认值:0
dict_demo = defaultdict(list) 默认值:空列表
dict_demo = defaultdict(dict) 默认值:空字典
dict_demo = defaultdict(tuple) 默认值:空元素
dict_demo = defaultdict(set) 默认值:空集合
当访问不存在的key时,会返回默认值,同时在字典中创建该记录。
deque
在数据结构中,队列和堆栈是两个非常重要的数据类型,一个先进先出,一个后进先出。在python中,使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
deque是为了高效实现插入和删除操作的双向链表结构,非常适合实现队列和堆栈这样的数据结构
功能:
deque是一个栈和队列的综合,发音为deck,可以成为双向队列。deque支持线程安全,两端都支持高效的入栈和出栈操作,时间复杂度为O(1)。
虽然list支持类似的操作,但它们针对快速的固定长度操作进行了优化,并为pop(0) 和insert(0,v)操作带来了O(n)内存移动成本,这两种操作都会更改基础数据表示的大小和位置。
如果未指定maxlen或为None,则deque可能会增长到任意长度。否则,deque将绑定到指定的最大长度。一旦有界长度deque已满,添加新项时,从另一端丢弃相应数量的项。
>>> from collections import deque
>>>
>>>
>>> my_deque = deque([1,2,3])
>>>
>>> my_deque.append(4)
>>>
>>> my_deque
deque([1, 2, 3, 4])
>>> my_deque.appendleft(0)
>>>
>>>
>>> my_deque
deque([0, 1, 2, 3, 4])
>>> my_deque.pop()
4
>>>
>>> my_deque.popleft()
0
>>>
>>> my_deque
deque([1, 2, 3])
deque拥有的方法
deque拥有众多的方法,因为deque
是一个双向链表,所以在头和尾都可以操作,方法有如下:deque.append(x)
在队列右边(尾部)插入xdeque.appendleft(x)
在队列左边(头部)插入xdeque.pop()
在队列左边(尾部)出栈deque.popleft()
在队列右边(头部)出栈deque.clear()
清空队列,初始化队列长度为1deque.copy()
创建一个浅拷贝的队列deque.count(x)
计算队列中x元素的个数deque.extend(iterable)
将可迭代对象扩展到队列右边deque.extendleft(iterable)
将可迭代对象扩展到队列左边deque.index(x)
返回元素x在队列中的位置,没有找到抛出异常ValueErrordeque.insert(i, x)
插入元素x到指定位置x。如果位置超出范围,抛出IndexError异常deque.remove(x)
删除第一个找到的元素x,没有元素抛出ValueError异常deque.reverse()
逆序deque.rotate(n)
将队列向右移动n个元素,如果n为负数则向左移动n个元素deque.maxlen()
返回队列长度,为空返回None。
namedtuple
命名元组类似于一个对象,包含只读属性的变量
功能:
命名元组为元组中的每个位置分配意义,并允许更可读、自文档化的代码。它们可以在任何使用常规元组的地方使用,并且它们增加了通过名称而不是位置索引访问字段的能力。
namedtuple创建一个和tuple类似的对象,而且对象可以通过名字访问。这对象更像带有数据属性的类,不过数据属性是只读的。
namedtuple(typename,field_names,*,verbose=False, rename=False, module=None)
1)typename:该参数指定所创建的命名元组的名字
2) field_names:该参数指定各个元素对应的名字,如 ['x','y'],也可以用"x,y"
简单使用
有一个长方体,长宽高的属性用一个元组来表示(12, 15, 16)。虽然三个数值能够表示一个长方体,但是这个元组并没有清楚的指明三个字段分别代表什么。其实三个字段分别是:
长 | 宽 | 高 |
---|---|---|
12 | 15 | 16 |
使用namedtuple来表示就能够很清晰的表示出来。
from collections import namedtuple
Cuboid = namedtuple("Cuboid", ["length", "width", "height"])
one = Cuboid(12, 15, 16)
>>> one[0]
12
>>> one.length
12
>>>
>>>
>>> one[1]
15
>>> one.width
15
>>>
>>>
>>> one[2]
16
>>> one.height
16
>>>
可以看出namedtuple的方法方式有如下关系:noe.length
== one[0]
one.width
== one[1]
one.height
== one[2]
三个方法
除了继承元组的方法,命名元组还支持三个额外的方法和两个属性
1._asdict()
返回一个新的 dict ,它将字段名称映射到它们对应的值
>>> one._asdict()
OrderedDict([('length', 12), ('width', 15), ('height', 16)])
2._replace
>>> two = one._replace(length=33)
>>> two.length
33
3._make
类方法,从存在的序列或迭代实例创建一个新实例。
>>> arr = [100,200,150]
>>>
KeyboardInterrupt
>>> three = Cuboid._make(arr)
>>> three
Cuboid(length=100, width=200, height=150)
>>> three[0]
100
>>> three.length
100
两个属性
1._fields
出了字段名
>>> one._fields
('length', 'width', 'height')
2.defaults
给字段设置默认值
>>> Cuboid = namedtuple("Cuboid", ["length", "width", "height"], defaults=[22,33,44])
>>> four = Cuboid()
>>> four[0]
22
>>> four.length
22
>>>
>>>
>>> four[1]
33
>>> four.width
33
OrderedDict
有顺序的字典和常规字典类似,但是有很多和排序有关的能力。但是有序字典现在变得不是那么重要了,因为在python3.7开始常规字典也能记住插入的顺序。
有序字典和常规字典仍然有一些不同之处:
- 常规字典被设计用于映射操作,保持插入顺序是其次的。
- 有序字典被设计用于排序操作,高效空间使用,迭代速度和更新操作是其次的
- 在算法上,有序字典处理频繁排序操作比常规字典更好,这个特性让有序字典可以适合追踪最近访问,如LRU
- OrderedDict 类的
popitem()
方法有不同的签名。它接受一个可选参数来指定弹出哪个元素 - OrderedDict 类有一个
move_to_end()
方法,可以有效地将元素移动到任一端
传统字典方法 | OrderedDict方法 | 差异 |
---|---|---|
clear | clear | |
copy | copy | |
fromkeys | fromkeys | |
get | get | |
items | items | |
keys | keys | |
pop | pop | |
popitem | popitem | OrderedDict 类的 popitem() 方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。 |
setdefault | setdefault | |
update | update | |
values | values | |
move_to_end | 可以有效地将元素移动到任一端。 |
Python3.6之前的dict是无序的,但是在某些情形我们需要保持dict的有序性,这个时候可以使用OrderedDict.
>>> from collections import OrderedDict
>>>
>>> ordict = OrderedDict()
>>>
>>> ordict['x'] = 200
>>> ordict['y'] = 300
>>> ordict['z'] = 100
>>> ordict['a'] = 400
>>>
>>>
>>> ordict
OrderedDict([('x', 200), ('y', 300), ('z', 100), ('a', 400)])
>>> ordict.pop('z')
100
>>> ordict
OrderedDict([('x', 200), ('y', 300), ('a', 400)])
两个特殊方法
1.popitem
删除头或尾
dic.popitem(): 删除尾元素
dic.popitem(False): 删除头元素
>>> ordict.popitem()
('a', 400)
>>> ordict.popitem(False)
('x', 200)
2.dic.move_to_end()
: 将指定键值对移动到尾部
>>> ordict
OrderedDict([('y', 300), ('z', 100), ('a', 1), ('b', 2)])
>>> ordict.move_to_end('y')
>>> ordict
OrderedDict([('z', 100), ('a', 1), ('b', 2), ('y', 300)])
ChainMap
ChainMap 映射链
功能:
ChainMap提供了一种多个字典整合的方式,它没有去合并这些字典,而是将这些字典放在一个列表(maps)里,内部实现了很多 dict 的方法,大部分 dict 的方法,ChainMap 都能使用。
from collections import ChainMap
a = {'x':1,'y':2}
b = {'x':100, 'z':200}
c = ChainMap(a,b)
>>> c
ChainMap({'x': 1, 'y': 2}, {'x': 100, 'z': 200})
>>> c.get('x')
1
>>> c.get('z')
200
>>>
在存储中类似于[a,b],即[{'x': 1, 'y': 2}, {'x': 100, 'z': 200}],这是ChainMap的特点,按照前后顺序存储每一个map
操作
1.读取
对ChainMap的读取会从第一个map开始读,直到遇到指定的key,返回第一个遇到的元素
2.更新
对ChainMap的修改只会影响第一个元素
>>> c["x"] = 33
>>>
>>> c
ChainMap({'x': 33, 'y': 2}, {'x': 100, 'z': 200})
拥有的方法
1.maps
一个可以更新的映射列表。这个列表是按照第一次搜索到最后一次搜索的顺序组织的。它是仅有的存储状态,可以被修改。列表最少包含一个映射。
ChainMap内部存储了一个名为maps的list用以维护mapping关系, 这个list可以直接查看和修改,修改之后相应ChainMap值也就修改了.
>>> c.maps
[{'x': 1, 'y': 2}, {'x': 100, 'z': 200}]
2.new_child
new_child(m=None, **kwargs)
返回一个新的 ChainMap,其中包含一个新的映射,后面跟随当前实例中的所有映射。 如果指定了 m,它会成为新的映射加在映射列表的前面;如果未指定,则会使用一个空字典,因此调用 d.new_child() 就等价于 ChainMap({}, *d.maps)。 如果指定了任何关键字参数,它们会更新所传入的映射或新的空字典。 此方法被用于创建子上下文,它可在不改变任何上级映射的情况下被更新。
>>> c.new_child()
ChainMap({}, {'x': 33, 'y': 2}, {'x': 100, 'z': 200})
3.parents
属性返回一个新的 ChainMap 包含所有的当前实例的映射,除了第一个。这样可以在搜索的时候跳过第一个映射。 使用的场景类似在 nested scopes 嵌套作用域中使用 nonlocal 关键词。用例也可以类比内建函数 super() 。
一个 d.parents 的引用等价于 ChainMap(*d.maps[1:]) 。
>>> c.parents
ChainMap({'x': 100, 'z': 200})
>>>
ChainMap只是对之前的字典做一个引用,因此,修改ChainMap会影响到之前的字典,同理修改原来的字典也会影响到ChainMap
c["x"] = 33
>>> c.new_child()
ChainMap({}, {'x': 33, 'y': 2}, {'x': 100, 'z': 200})
>>> a
{'x': 33, 'y': 2}
>>>
>>> a['x'] = 44
>>> c
ChainMap({'x': 44, 'y': 2}, {'x': 100, 'z': 200})
总结
collections号称是基本数据结构的增强版,如果基本数据结构是iphone13,那collections就是iphone13 pro,iphone13 pro max。可以将是否熟练使用collections作为python进阶编程的一个衡量标准。