collection库的用法

collection在英文当中有容器的意思,所以顾名思义,这是一个容器的集合。这个库当中的容器很多,有一些不是很常用,本篇文章选择了其中最常用的几个,一起介绍给大家。

defaultdict

defaultdict可以说是这个库当中使用最简单的一个,并且它的定义也很简单,我们从名称基本上就能看得出来。它本质上就是一个dict,它是dict的优化版,能够解决的是我们使用dict当中最常见的问题,就是key为空的情况。

使用dict

在正常情况下,我们在dict中获取元素的时候,都需要考虑key为空的情况。如果不考虑这点,那么当我们获取了一个不存在的key,会导致系统抛出异常。我们当然可以在每次get之前写一个if判断,但是这很麻烦,比如:

 if key in dict:
     return dict[key]
 else:
     return None

当然,这是最笨的方法,dict当中为我们提供了带默认值的get方法。比如,我们可以写成:

 return dict.get(key, None)

这样,当key不在dict当中存在的时候,会自动返回我们设置的默认值。这个省去了很多麻烦的判断,但是在一些特殊情况下仍然存在一点问题。举个例子,比如当key存在重复,我们希望将key相同的value存进一个list当中,而不是只保留一个。这种情况下写成代码就会比较复杂:

 data = [(1, 3), (2, 1), (1, 4), (2, 5), (3, 7)]
 d = {}
 for k, v in data:
     if k in d:
         d[k].append(v)
     else:
         d[k] = [v]

由于dict的value是一个list,所以我们还是需要判断是否为空,不能直接使用默认值,间接操作当然可以,但是还是不够简单:

 for k, v in data:
     cur = d.get(k, [])
     cur.append(v)
     d[k] = c

这和使用if区别并不大,为了完美解决这个问题,我们可以使用collections当中的defaultdict

defaultdict用法:

使用defaultdict之后,如果key不存在,容器会自动返回我们预先设置的默认值。

其他的使用和dict一摸一样,它也具有defaultdict.keys()defaultdict.values() 以及defaultdict.items()

默认值为list

默认值为list是defaultdict用的最多的场景

其基本用法为:

 from collections import defaultdict
 d = defaultdict(list)
 ​
 for k, v in data:
     d[k].append(v)

例子:

 from collections import defaultdict
 d = defaultdict(list)
 s = [('yellow',1),('blue',2),('yellow',3),('blue',4),('red',5)]
 ​
 for k, v in s:
     d[k].append(v)
     
 print(d)
 # defaultdict(<class 'list'>, {'yellow': [1, 3], 'blue': [2, 4], 'red': [5]})
 print(d.keys())
 # dict_keys(['yellow', 'blue', 'red'])
 print(d.values())
 # dict_values([[1, 3], [2, 4], [5]])
 print(d.items())
 # dict_items([('yellow', [1, 3]), ('blue', [2, 4]), ('red', [5])])

上述例子中,defaultdict(list)代表如果key不存在,容器会自动生成我们预先设置的默认值,所以当一个新的key出现时,它的value自动就被创建成了一个空list [],所以其用法十分简洁。不用再去用其他的判断了。

默认值为int

当然默认值也可以是int

defaultdict(int)如果key不存在,自动生成0

 s = "mississippi"
 d = defaultdict(int)
 for ch in s: 
     d[ch]+=1
  
 print(d)
 # defaultdict(<class 'int'>, {'m': 1, 'i': 4, 's': 4, 'p': 2})

默认值为set

当然默认值也可以是set,目的是为了去重

defaultdict(set)如果key不存在,自动生成空集合set()

 s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue',
 4)]
 d = defaultdict(set)
 for k,v in s:
     d[k].add(v)
 ​
 print(d)
 # defaultdict(<class 'set'>, {'red': {1, 3}, 'blue': {2, 4}})

自定义默认值

需要注意的是defaultdict传入的默认值可以是一个数据类型也可以是一个自定义函数。如果我们传入int,那么默认值会被设置成int()的结果,也就是0,如果我们想要自定义或者修改,我们可以传入一个自定义函数,比如:

# 将自定义默认值为3
d = defaultdict(lambda : 3)
data = [(1, 3), (2, 1), (1, 4), (2, 5), (3, 7)]
for k, v in data:
    d[k] += v
print(d)
# defaultdict(<function <lambda> at 0x000001BA65046280>, {1: 10, 2: 9, 3: 10})
# 解释:1 = 3(默认)+3 +4 =10, 2 = 3(默认)+1+5=9

与原生dic之间的转化

如果想把一个defaultdict变为dict,只需要简单的使用dict()函数即可

但是要把dict变为defaultdict只能通过遍历的方式,注意不能直接defaultdict(dic)(其中dic是一个已有的字典),因为defaultdict的参数是传入的默认值,它只可以是一个数据类型也可以或一个自定义函数,并不能是一个已有的字典

如:

dic = {'m': 1, 'i': 4, 's': 4, 'p': 2}
d = defaultdict(int)
# `dict`变为`defaultdict`只能通过遍历的方式
for k, v in dic.items():
    d[k] += v
    
print(d)
# defaultdict(<class 'int'>, {'m': 1, 'i': 4, 's': 4, 'p': 2})

# 使用`dict()`函数直接可以把`defaultdict`变为`dict`
print(dict(d) == dic)
# True

Counter

这是一个非常常用和非常强大的工具,我们经常用到。顾名思义,Counter就是个计数器的数据结构

在我们实际的编程当中,我们经常遇到一个问题,就是计数和排序特别是在nlp任务中使用Counter要比自己手动用dict实现简洁的多,比如说我们在分析文本的时候,会得到一堆单词。其中可能有大量的长尾词,在整个文本当中可能只出现过寥寥几次。于是我们希望计算一下这些单词出现过的数量,只保留出现次数最高的若干个。

这个需求让我们自己实现当然也不困难,我们完全可以创建一个dict,然后对这些单词一个一个遍历。原本我们还需要考虑单词之前没有出现过的情况,如果我们上面说的defaultdict,又要简单许多。但是我们还是少不了计数然后排序的步骤,如果使用Counter这个步骤会缩减成一行代码。

一个 Counter 是一个dict的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数。

Counter对象和其他的使用和defaultdict一样,它也继承了字典的所有方法,如Counter.keys()Counter.values() 以及Counter.items()。只不过它重写了dict中的一些函数并添加了一些新的函数

从现有数据结构转换到Counter

元素从一个 iterable 被计数或从已有的 countermapping初始化。

如果是iterable是字符串、列表、集合(不过对集合计数的意义不大,因为肯定只有一个)则会直接对其计数并返回计数结果。

如果是从dict或已有的counter转化那么只会转化为counter类型。

mapping指的是从一个类似于**kwargs的方式初始化counter

返回的显示顺序默认是按照value的从大到小顺序,不过counter本质是一个dict,其实际上是无序的,所以只能通过key进行访问

# 创建一个空counter
c = Counter() 

# 从字符串转化为counter,会对每个ch进行计数并返回counter
c = Counter('gallahad') 
# Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})

# 从列表转化为counter,会对每个元素进行计数并返回counter
c = Counter(['apple', 'apple', 'pear', 'watermelon', 'pear', 'peach'])
# Counter({'apple': 2, 'pear': 2, 'watermelon': 1, 'peach': 1})

# 从集合转化为counter,会对每个元素进行计数并返回counter
c = Counter({1,2,3,4,3,4})
# Counter({1: 1, 2: 1, 3: 1, 4: 1}),意义不大,因为集合计数每个元素数量一定为1

# 从字典转化为counter
c = Counter({'red': 4, 'blue': 2})      # a new counter from a mapping
# Counter({'red': 4, 'blue': 2})

# 从`mapping`类似于`**kwargs`的方式初始化counter
c = Counter(cats=4, dogs=8)  
# Counter({'dogs': 8, 'cats': 4})

# 从已有Counter转化
c = Counter(Counter({'red': 4, 'blue': 2}))
# Counter({'red': 4, 'blue': 2})

与dict的不同处

Counter与字典还有个不同的地方,它有一个字典接口,如果引用的键没有任何记录,就返回一个0,而不是弹出一个 KeyError :

c = Counter(['eggs', 'ham'])
# 对于不存在的key返回0而不是报错
c['bacon']                             
# 0

设置一个计数为0不会从计数器中移去一个元素。使用 del 来删除它:

>>> c['sausage'] = 0                        # counter entry with a zero count
>>> del c['sausage']                        # del actually removes the entry
12

在 3.7 版更改: 作为dict的子类,Counter 继承了记住插入顺序的功能。 Counter 对象进行数学运算时同样会保持顺序。 结果会先按每个元素在运算符左边的出现时间排序,然后再按其在运算符右边的出现时间排序。

Counter对象除了继承了字典的方法以外,还提供了四个其他函数:

elements()most_common(n)subtract()update()

以及+-等运算符

常用函数以及运算符

elements()

返回一个迭代器,我们需要用list()进行打开。

其中每个元素将重复出现计数值所指定次。 元素会按首次出现的顺序返回。配合使用sorted()可以返回有序的列表。

如果一个元素的计数值小于1,elements() 将会忽略它。

c = Counter( b=2, a=4, c=0, d=-2)
c.elements()
# <itertools.chain object at 0x0000023160DF4670>本身返回迭代器,需要用list()打开
list(c.elements())
# ['b', 'b', 'a', 'a', 'a', 'a'] 按首次出现的顺序返回
sorted(c.elements())
# ['a', 'a', 'a', 'a', 'b', 'b'] 返回有序的

most_common(n)

返回一个列表,其中包含 n 个最常见的元素及出现次数的元组 即 (element,count)对。 按常见程度由高到低排序。 如果 n 被省略或为 Nonemost_common()将返回计数器中的所有元素

计数值相等的元素按首次出现的顺序排序

Counter('abracadabra').most_common(3)
# [('a', 5), ('b', 2), ('r', 2)]

Counter('abracadabra').most_common()
# 如果 n 被省略或为 `None`,`most_common()`将返回计数器中所有元素。 
# [('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]

update()

update()可以将两个Counter相加,它会自动将两个Counter合并,相同的key对应的value累加。

从 迭代对象 计数元素或者 从另一个 映射对象 (或计数器) 添加。 但是字典的用法 dict.update() 是替换,而不是加上。另外,迭代对象 应该是序列元素,而不是一个 (key, value) 对。

同样:输入和输出都可以是0或者负数

c = Counter(a=3, b=1)
d = Counter(a=-4, b=2)
c.update(d)
c
# Counter({'b': 3, 'a': -1})

subtract()

update()可以将两个Counter相减,它会自动将两个Counter合并,相同的key对应的value相减。

从 迭代对象 或 映射对象 减去元素。像 dict.update() 但是是减去,而不是替换输入和输出都可以是0或者负数

c = Counter(a=4, b=2, c=0, d=-2)
d = Counter(a=1, b=2, c=3, d=4)
c.subtract(d)
c
# Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})

数学符号运算

Counter对象还提供了几个数学操作,+-,通过加上或者减去元素的相应计数。 &|,交集和并集返回相应计数的最小或最大值。 注意:符号操作都可以接受带符号的计数的输入,但是不同于前面的函数subtract()和update(),其输出会忽略掉结果为零或者小于零的计数

c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
## 通过符号运算的结果只保留正数!!!##

# 相加 c[x] + d[x] 对应subtract函数,结果只保留正数
c + d                      
# Counter({'a': 4, 'b': 3})

# 相减 c[x] - d[x] 对应subtract函数,结果只保留正数
c - d                     
# Counter({'a': 2})
# b:-1是负数输出会直接忽略

# intersection交集:  min(c[x], d[x]) ,结果只保留正数
c & d                     
# Counter({'a': 1, 'b': 1})

# union并集:  max(c[x], d[x]),结果只保留正数
c | d                       
# Counter({'a': 3, 'b': 2})

单目符号操作符意思是取计数器的所有正数(‘+Counter’)或倒数(‘-Counter’)去除0以及负数

c = Counter(a=2, b=-4,c=0)

+c
# Counter({'a': 2})  可以简单理解为只返回Counter中的正数

-c
#Counter({'b': 4}) 可以简单理解为只返回Counter中的负数,并取绝对值

其他常用案例

sum(c.values())                 # total of all counts
c.clear()                       # reset all counts
list(c)                         # list unique elements
set(c)                          # convert to a set
dict(c)                         # convert to a regular dictionary
c.items()                       # convert to a list of (elem, cnt) pairs
Counter(dict(list_of_pairs))    # convert from a list of (elem, cnt) pairs
c.most_common()[:-n-1:-1]       # n least common elements 倒序
+c                              # remove zero and negative counts
123456789

deque

我们都知道queue是队列,deque也是队列,不过稍稍特殊一些,是双端队列。对于queue来说,只允许在队尾插入元素,在队首弹出元素。而deque既然称为双端队列,那么说明它的队首和队尾都支持元素的插入和弹出。相比于普通的队列,要更加灵活一些。

除了常用的clear、copy、count、extend等api之外,deque当中最常用也是最核心的api还有append、pop、appendleft和popleft。从名字上我们就看得出来,append和pop和list的append和pop一样,而appendleft和popleft则是在队列左侧,也就是头部进行pop和append的操作。非常容易理解。

在日常的使用当中,真正用到双端队列的算法其实不太多。大多数情况下我们使用deque主要有两个原因,第一个原因是deque收到GIL的管理,它是线程安全的。而list则没有GIL锁,因此不是线程安全的。也就是说在并发场景下,list可能会导致一致性问题,而deque不会。另一个原因是deque支持固定长度,当长度满了之后,当我们继续append时,它会自动弹出最早插入的数据。

比如说当我们拥有海量的数据,我们不知道它的数量,但是想要保留最后出现的指定数量的数据的时候,就可以使用deque。

from collections import deque
dque = deque(maxlen=10)
# 假设我们想要从文件当中获取最后10条数据
for i in f.read():
    dque.append(i)

namedtuple

namedtuple很特殊,它涉及到元编程的概念。简单介绍一下元编程的概念,我们不做过多的深入。简而言之,就是在常见的面向对象当中。我们都是定义类,然后通过类的构造函数来创建实例。而元编程指的是我们定义元类,根据元类创建出来的并不是一个实例,而是一个类。如果用模具和成品来分别比喻类和实例的话,元类相当于是模具的模具

namedtuple是一个非常简单的元类,通过它我们可以非常方便地定义我们想要的类。

它的用法很简单,我们直接来看例子。比如如果我们想要定义一个学生类,这个类当中有name、score、age这三个字段,那么这个类会写成:

class Student:
    def __init__(self, name=None, score=None, age=None):
        self.name = name
        self.score = score
        self.age = age

这还只是粗略的写法,如果考虑规范,还需要定义property等注解,又需要很多代码。如果我们使用namedtuple可以简化这个工作,我们来看代码:

from collections import namedtuple
# 这个是类,columns也可以写成'name score age',即用空格分开
Student = namedtuple('Student', ['name', 'score', 'age'])

# 这个是实例
student = Student(name='xiaoming', score=99, age=10)
print(student.name)

通过使用namedtuple,我们只需要一行就定义了一个类,但是这样定义的类是没有缺失值的,但是namedtuple很强大,我们可以通过传入defaults参数来定义缺失值。

Student = namedtuple('Student', ['name', 'score', 'age'], defaults=(0, 0))

可以注意到,虽然我们定义了三个字段,但是我们只设置了两个缺失值。在这种情况下,namedtuple会自动将缺失值匹配上score和age两个字段。因为在Python的规范当中,必选参数一定在可选参数前面。所以nuamdtuple会自动右对齐。

Guess you like

Origin blog.csdn.net/Joey9898/article/details/121940358