Python原生数据结构增强模块collections

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) 在队列右边(尾部)插入x
deque.appendleft(x) 在队列左边(头部)插入x
deque.pop() 在队列左边(尾部)出栈
deque.popleft() 在队列右边(头部)出栈
deque.clear() 清空队列,初始化队列长度为1
deque.copy() 创建一个浅拷贝的队列
deque.count(x) 计算队列中x元素的个数
deque.extend(iterable) 将可迭代对象扩展到队列右边
deque.extendleft(iterable)将可迭代对象扩展到队列左边
deque.index(x) 返回元素x在队列中的位置,没有找到抛出异常ValueError
deque.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开始常规字典也能记住插入的顺序。
有序字典和常规字典仍然有一些不同之处:

  1. 常规字典被设计用于映射操作,保持插入顺序是其次的。
  2. 有序字典被设计用于排序操作,高效空间使用,迭代速度和更新操作是其次的
  3. 在算法上,有序字典处理频繁排序操作比常规字典更好,这个特性让有序字典可以适合追踪最近访问,如LRU
  4. OrderedDict 类的 popitem() 方法有不同的签名。它接受一个可选参数来指定弹出哪个元素
  5. 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进阶编程的一个衡量标准。

参考:https://zhuanlan.zhihu.com/p/343747724

posted @ 2022-01-06 11:46  金色旭光  阅读(504)  评论(0编辑  收藏  举报