写点什么

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

  • 2022 年 1 月 06 日
  • 本文字数:5670 字

    阅读完需:约 19 分钟

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)>>> resCounter({'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)>>> resCounter({2: 2, 3: 2, 4: 2, 1: 1, 5: 1})
复制代码

字典

>>> map = {"a":3, "b":2, "c":1}>>> res = Counter(map)>>> resCounter({'a': 3, 'b': 2, 'c': 1})
复制代码

元组

>>> tu = (1,2,3,2,3,4,2,3,5)>>> res = Counter(tu)>>> resCounter({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)>>> resCounter({1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1})
复制代码

Counter 拥有的方法

1.elements()

返回一个迭代器,其中每个元素将重复出现计数值所指定次。元素返回的顺序是按照元素在原本序列中首次出现的顺序

>>> str = "hello world">>> res = Counter(str)>>> resCounter({'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)>>> resCounter({'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")>>> aCounter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})>>> b = Counter("hello")>>> a.subtract(b)>>> aCounter({'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_demodefaultdict(<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_demodefaultdict(<class 'int'>, {'a': 5, 'b': 2, 'c': 3})
复制代码

默认值的类型

dict_demo = defaultdict(str)  默认值:空字符串dict_demo = defaultdict(int)  默认值:0dict_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_dequedeque([1, 2, 3, 4])>>> my_deque.appendleft(0)>>> >>> >>> my_dequedeque([0, 1, 2, 3, 4])>>> my_deque.pop()4>>> >>> my_deque.popleft()0>>> >>> my_dequedeque([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)。虽然三个数值能够表示一个长方体,但是这个元组并没有清楚的指明三个字段分别代表什么。其实三个字段分别是:

使用 namedtuple 来表示就能够很清晰的表示出来。

from collections import namedtuple
Cuboid = namedtuple("Cuboid", ["length", "width", "height"])one = Cuboid(12, 15, 16)>>> one[0]12>>> one.length12>>> >>> >>> one[1]15>>> one.width15>>> >>> >>> one[2]16>>> one.height16>>>
复制代码

可以看出 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.length33
复制代码

3._make

类方法,从存在的序列或迭代实例创建一个新实例。

>>> arr = [100,200,150]>>> KeyboardInterrupt>>> three = Cuboid._make(arr)>>> threeCuboid(length=100, width=200, height=150)>>> three[0]100>>> three.length100
复制代码

两个属性

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.length22>>> >>> >>> four[1]33>>> four.width33
复制代码

OrderedDict

有顺序的字典和常规字典类似,但是有很多和排序有关的能力。但是有序字典现在变得不是那么重要了,因为在 python3.7 开始常规字典也能记住插入的顺序。

有序字典和常规字典仍然有一些不同之处:

popitem()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>>> >>> >>> ordictOrderedDict([('x', 200), ('y', 300), ('z', 100), ('a', 400)])>>> ordict.pop('z')100>>> ordictOrderedDict([('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() : 将指定键值对移动到尾部

>>> ordictOrderedDict([('y', 300), ('z', 100), ('a', 1), ('b', 2)])>>> ordict.move_to_end('y')>>> ordictOrderedDict([('z', 100), ('a', 1), ('b', 2), ('y', 300)])
复制代码

ChainMap

ChainMap 映射链

功能:

ChainMap 提供了一种多个字典整合的方式,它没有去合并这些字典,而是将这些字典放在一个列表(maps)里,内部实现了很多 dict 的方法,大部分 dict 的方法,ChainMap 都能使用。

from collections import ChainMapa = {'x':1,'y':2}b = {'x':100, 'z':200}c = ChainMap(a,b)>>> cChainMap({'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>>> >>> cChainMap({'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.parentsChainMap({'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>>> cChainMap({'x': 44, 'y': 2}, {'x': 100, 'z': 200})
复制代码

总结

collections 号称是基本数据结构的增强版,如果基本数据结构是 iphone13,那 collections 就是 iphone13 pro,iphone13 pro max。可以将是否熟练使用 collections 作为 python 进阶编程的一个衡量标准。

原文链接: http://www.cnblogs.com/goldsunshine/p/15760807.html

发布于: 2022 年 01 月 06 日阅读数: 120
用户头像

需要资料添加小助理vx:bjmsb1226 2021.10.15 加入

爱生活爱编程

评论

发布
暂无评论
Python原生数据结构增强模块collections_Python_Java全栈架构师_InfoQ写作社区