3

Python原生数据结构增强模块collections_Python_Java全栈架构师_InfoQ写作社区

 2 years ago
source link: https://xie.infoq.cn/article/a4aa67e8ea7957c9694e96584
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 namedtupleCuboid = 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

对 ChainMap 的读取会从第一个 map 开始读,直到遇到指定的 key,返回第一个遇到的元素

对 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK