对于刚上手的Pythoners来说可能还没有接触到过collections,不过完全没有关系,如果实在感觉有难度,可以先收藏,以后食用,因为下面我们会联系到Leetcode上面的例题来讲解几个collections,让你能够深刻的理解collections,在以后运用中能够简化问题,快速求解。
1.Colleciton概述:
collections 模块实现了特定目标的容器,以提供Python标准内建容器dict ,list , set , 和tuple的替代选择。在计数等问题中使用collection可以大大提高效率。collections一共包括了九个子类,列举如下。
官方资料:collections --- 容器数据类型 — Python 3.13.1 文档
| 一个工厂函数,用来创建元组的子类,子类的字段是有名称的。 | |
| 类似列表的容器,但 append 和 pop 在其两端的速度都很快。 | |
| 类似字典的类,用于创建包含多个映射的单个视图。 | |
| 用于计数 hashable 对象的字典子类 | |
| 字典的子类,能记住条目被添加进去的顺序。 | |
| 字典的子类,通过调用用户指定的工厂函数,为键提供默认值。 | |
| 封装了字典对象,简化了字典子类化 | |
| 封装了列表对象,简化了列表子类化 | |
| 封装了字符串对象,简化了字符串子类化 |
#在使用前一定要记得引入
import collections
from collections import *
2.1 计数器-Counter
2.11概述:
一个计数器工具提供快速和方便的计数,Counter是一个dict的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数,他其实就是一个哈希映射,可以是几乎任何对象 ,Counter类有点像其他语言中的bags或multisets。简单说,就是可以统计计数,他的初始值都是0,来几个例子看看就清楚了,比如
from collections import Counter
cnt = Counter()
#在使用Counter一般在for 循环中
#如对s = 'abcdefghick'中统计各个字母数目:
s = 'abcdefghick'
for i in s:
cnt[i] += 1
print(cnt)
#如果不是在for中用,直接初始化也合法
#Eg:
cnt2 = Counter(s)
#cnt['数学'] 也是合法的,因此你几乎可以统计任何数据不仅仅只是字母
结果如下
Counter({'c': 2, 'a': 1, 'b': 1, 'd': 1, 'e': 1, 'f': 1, 'g': 1, 'h': 1, 'i': 1, 'k': 1})
那么如果当你要访问最大值呢怎么操作呢?你肯定会想到max(cnt[])但实际上直接对其求max返回的是值最大的“键” 而不是值,这里会用到values
s = 'abcdefghick'
cnt = Counter(s)
max(cnt)
#这个返回的是‘c’因为c的次数最多
#如果要访问最大的值:
max(cnt.values())
#这个表示的是最大的数值是2
2.12.方法一:elements()
返回一个迭代器,其中每个元素将重复出现计数值所指定次。 元素会按首次出现的顺序返回。 如果一个元素的计数值小于一,elements() 将会忽略它。
c = Counter(a=4, b=2, c=0, d=-2)
>>> sorted(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
2.13.方法二:most_common([n])
返回一个列表,其中包含 n 个最常见的元素及出现次数,按常见程度由高到低排序。 如果 n 被省略或为 None,most_common() 将返回计数器中的 所有 元素。 计数值相等的元素按首次出现的顺序排序:
>>> Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]
这在Leetcode也有题目
Eg1:前K个高频数:
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
from collections import Counter #这个在Leetcode中可以不用写,但养成习惯
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
ans = []
for i in Counter(nums).most_common(k):#对前k个数遍历
# Counter('abracadabra').most_common(3)
x,val = i #涉及解包
ans.append(x)
return ans
举一反三:(下面这道题目类似,但是要注意一点,如果次数相同按照字母顺序排序输出,只需要看K+1是否是与前面相同,如果相同就继续往深处看,欢迎大家在评论区讨论)
Eg2:前k个高频词:给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。
示例 1:
输入: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
输出: ["the", "is", "sunny", "day"]
解析: "the", "is", "sunny" 和 "day" 是出现次数最多的四个单词,
出现次数依次为 4, 3, 2 和 1 次。
2.14方法三 del &subtract(iterable or mapping):
这个是对于要删除的映射,当然del很多数据都可以有这一种方法,只不过一般的del都是直接删除掉了,再次访问就是Not defined,而在计数器中的del不过是置零。与subtrack要区别开,一般del要么删除某一个映射的值(也不是完全删除,就是让相应的value 变为 0或者整个计数器,而subtrack可以实现无需手动的部分删除
,subtract([iterable-or-mapping]),减去一个 可迭代对象 或 映射对象 (或 counter) 中的元素。类似于 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})
下面有道题用到了del,我们可以从题目中去了解del的用法的妙处
Eg3:给你一个整数数组 nums 和一个整数 k 。请你从 nums 中满足下述条件的全部子数组中找出最大子数组和:
- 子数组的长度是
k,且 - 子数组中的所有元素 各不相同 。
返回满足题面要求的最大子数组和。如果不存在子数组满足这些条件,返回 0 。
子数组 是数组中一段连续非空的元素序列。
from collections import Counter
class Solution:
def maximumSubarraySum(self, nums: List[int], k: int) -> int:
s = sum(nums[:k-1]) #滑动窗口的初始化,
ans = 0
cnt = Counter(nums[:k-1]) #只统计 k - 1个数,这样下面的for进去in之后的窗口长度满足题目
for out_,in_ in zip(nums,nums[k-1:]): #滑动窗口进去的值和出去的值
#in:
s += in_
cnt[in_] += 1
if len(cnt) == k: 判断
ans = max(ans,s)
#out:
s -= out_
cnt[out_] -= 1
if cnt[out_] == 0:
del cnt[out_]
return ans
2.15方法四:total()
计算总计数值。计算所有数值大家肯定会想到用sum实际上PythonCounter中直接用sum是错误的
因为即有键和值,字符串无法加,但是sum(c.values())又是合法且正确的
>>> c = Counter(a=10, b=5, c=0)
>>> c.total()
15
>>> sum(c.values())
15
##实际上sum(c)会报错,但是用了sum(c.values())就不会,你可以联系前面讲到了的max(c.valuse())
2.16常用的其他方法:
c.total() # 所有计数的总和
c.clear() # 重置所有计数
list(c) # 列出不同的元素
set(c) # 转换为集合
dict(c) # 转换为常规字典
c.items() # 访问 (元素, 计数) 对
Counter(dict(list_of_pairs)) # 转换自 (元素, 计数) 对的列表
c.most_common()[:-n-1:-1] # n 个最不常见的元素
+c # 移除为零和负的计数
2.17数学运算:
除上述之外,Python也提供数学上的运算,类似集合。提供了几种数学运算用来合并 Counter 对象,产生多集(所有计数值均大于零的 counter)。加减运算通过增加或减少两者间对应元素的计数来合并 counter。交并运算返回对应计数的最小值和最大值。相等和包含运算比较对应的计数。每个运算的参数都可以含有有符号的计数,但输出将排除计数小于等于零的元素。
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c + d # 将两个计数器相加: c[x] + d[x]
c - d # 相减(只保留为正的计数)
c & d # 交集: min(c[x], d[x])
c | d # 并集: max(c[x], d[x])
c == d # 相等: c[x] == d[x]
c <= d # 包括: c[x] <= d[x]
3.1类字典-defaultdict
class collections.defaultdict(default_factory=None, )返回一个新的类似字典的对象。 defaultdict 是内置 dict 类的子类。 它重写了一个方法并添加了一个可写的实例变量。 其余的功能与 dict 类相同因而不在此文档中写明。本对象包含一个名为 default_factory 的属性,构造时,第一个参数用于为该属性提供初始值,默认为None,或者说是0。所有其他参数(包括关键字参数)都相当于传递给 dict 的构造函数.。
3.11list:
使用 list 作为 default_factory,很轻松地将(键-值对组成的)序列转换为(键-列表组成的)字典:
>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
d[k].append(v)
>>> sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
当每个键首次被遇到时,它还不在映射之中;所以会使用 default_factory 函数自动创建一个条目,该函数返回一个空的 list。 随后将使用 list.append() 操作将值添加到这个新列表中。 当再次遇到该键时,将正常地执行查找并且 list.append() 操作会将另一个值添加到列表中。 这个做法相比使用 dict.setdefault() 的等价做法更简单更快速:
>>> d = {}
>>> for k, v in s:
... d.setdefault(k, []).append(v)
...
>>> sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
3.12int
设置 default_factory 为 int,使 defaultdict 用于计数(类似其他语言中的 bag 或 multiset):
>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
... d[k] += 1
...
>>> sorted(d.items())
[('i', 4), ('m', 1), ('p', 2), ('s', 4)]
当一个字母首次遇到时,它会查询失败,则 default_factory 会调用 int() 来提供一个整数 0 作为默认值。后续的自增操作建立起对每个字母的计数。
函数 int() 总是返回 0,这是常数函数的特殊情况。一个更快和灵活的方法是使用 lambda 函数,可以提供任何常量值(不只是0):
>>> def constant_factory(value):
... return lambda: value
...
>>> d = defaultdict(constant_factory('<missing>'))
>>> d.update(name='John', action='ran')
>>> '%(name)s %(action)s to %(object)s' % d
'John ran to <missing>'
3.13构建集合
设置 default_factory 为 set 使 defaultdict 用于构建 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)
...
>>> sorted(d.items())
[('blue', {2, 4}), ('red', {1, 3})]
下面用例题来讲解其中用到的int
Eg3:给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数:
- 子串中不同字母的数目必须小于等于
maxLetters。 - 子串的长度必须大于等于
minSize且小于等于maxSize。
from collections import Counter
class Solution:
def maxFreq(self, s: str, maxLetters: int, minSize: int, maxSize: int) -> int:
n = len(s)
cnt = collections.defaultdict(int) #调用colleciotns里面的字典库来计数,把没有出现过的子字符串都初始化为int的初始变量
for i in range(n - minSize + 1):
temp = s[i:minSize + i]
c = set(temp) #利用到了集合的去重性
if len(c) <= maxLetters:
cnt[temp] += 1
return max(cnt.values()) if cnt else 0
# 本题的最大长度没有用到,因为如果这个字符串本身就是重复的字串一肯定重复
4.1 namedtuple命名元组的工厂函数
4.11概述
命名元组赋予每个位置一个含义,提供可读性和自文档性。它们可以用于任何普通元组,并添加了通过名字获取值的能力,通过索引值也是可以的。
collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
返回一个新的元组子类,名为 typename 。这个新的子类用于创建类元组的对象,可以通过字段名来获取属性值,同样也可以通过索引和迭代获取值。子类实例同样有文档字符串(类名和字段名)另外一个有用的 __repr__() 方法,以 name=value 格式列明了元组内容。
field_names 是一个像 [‘x’, ‘y’] 一样的字符串序列。另外 field_names 可以是一个纯字符串,用空白或逗号分隔开元素名,比如 'x y' 或者 'x, y' 。
任何有效的Python 标识符都可以作为字段名,除了下划线开头的那些。有效标识符由字母,数字,下划线组成,但首字母不能是数字或下划线,另外不能是关键词 keyword 比如 class, for, return, global, pass, 或 raise 。
如果 rename 为真, 无效字段名会自动转换成位置名。比如 ['abc', 'def', 'ghi', 'abc'] 转换成 ['abc', '_1', 'ghi', '_3'] , 消除关键词 def 和重复字段名 abc 。
defaults 可以为 None 或者是一个默认值的 iterable 。如果一个默认值域必须跟其他没有默认值的域在一起出现,defaults 就应用到最右边的参数。比如如果域名 ['x', 'y', 'z'] 和默认值 (1, 2) ,那么 x 就必须指定一个参数值 ,y 默认值 1 , z 默认值 2 。
如果定义了 module,则命名元组的 __module__ 属性将被设为该值。
具名元组实例毋需字典来保存每个实例的不同属性,所以它们轻量,占用的内存和普通元组一样。
要支持封存操作,应当将命名元组类赋值给一个匹配 typename 的变量。
>>> # 基本示例
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22) # 使用位置或关键字参数进行实例化
>>> p[0] + p[1] # 像普通元组 (11, 22) 一样可索引
33
>>> x, y = p # 像普通元素一样解包
>>> x, y
(11, 22)
>>> p.x + p.y # 字段也可按名称访问
33
>>> p # 名称=值 风格的易读的 __repr__
Point(x=11, y=22)
4.2方法一csv:
命名元组尤其有用于赋值 csv sqlite3 模块返回的元组
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
print(emp.name, emp.title)
import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
print(emp.name, emp.title)
4.3方法二-somenamedtuple:
classmethod somenamedtuple._make(iterable)
除了继承元组的方法,命名元组还支持三个额外的方法和两个属性。为了
防止字段名冲突,方法和属性以下划线开始。类方法从存在的序列或迭代实例创建一个新实例。
>>> t = [11, 22]
>>> Point._make(t)
Point(x=11, y=22)
somenamedtuple._asdict()
>>> p = Point(x=11, y=22)
>>> p._asdict()
{'x': 11, 'y': 22}
4.4方法三replacesomenamedtuple._replace(**kwargs)
返回一个新的命名元组实例,并将指定域替换为新的值
>>> p = Point(x=11, y=22)
>>> p._replace(x=33)
Point(x=33, y=22)
>>> for partnum, record in inventory.items():
... inventory[partnum] = record._replace(price=newprices[partnum], timestamp=time.now())
4.41泛型函数 copy.replace()
copy.replace也支持具名元组
符串元组列出了字段名。用于提醒和从现有元组创建一个新的命名元组类
>>> p._fields # 查看字段名
('x', 'y')
>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)
somenamedtuple._field_defaults
字典将字段名称映射到默认值。
>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._field_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)
4.5方法四 getattr() 函数 :
获取这个名字域的值
>>> getattr(p, 'x')
11
转换一个字典到命名元组,使用 ** 两星操作符 (所述如 解包实参列表):
>>> d = {'x': 11, 'y': 22}
>>> Point(**d)
Point(x=11, y=22)
因为一个命名元组是一个正常的Python类,它可以很容易的通过子类更改功能。这里是如何添加一个计算域和定宽输出打印格式:
>>> class Point(namedtuple('Point', ['x', 'y'])):
... __slots__ = ()
... @property
... def hypot(self):
... return (self.x ** 2 + self.y ** 2) ** 0.5
... def __str__(self):
... return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
>>> for p in Point(3, 4), Point(14, 5/7):
... print(p)
Point: x= 3.000 y= 4.000 hypot= 5.000
Point: x=14.000 y= 0.714 hypot=14.018
5.1OrderedDict:
5.11概述:
有序词典就像常规词典一样,但有一些与排序操作相关的额外功能。由于内置的 dict 类获得了记住插入顺序的能力(在 Python 3.7 中保证了这种新行为),它们变得不那么重要了。
一些与 dict 的不同仍然存在:
-
常规的 dict 被设计为非常擅长映射操作。 跟踪插入顺序是次要的。
-
OrderedDict 旨在擅长重新排序操作。 空间效率、迭代速度和更新操作的性能是次要的。
-
OrderedDict 算法能比 dict 更好地处理频繁的重排序操作。 如下面的例程所示,这使得它更适用于实现各种 LRU 缓存。
-
对于 OrderedDict ,相等操作检查匹配顺序。
常规的 dict 可以使用
p == q and all(k1 == k2 for k1, k2 in zip(p, q))进行模拟顺序相等性测试。 -
OrderedDict 类的
popitem()方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。常规的 dict 可以使用
d.popitem()模拟 OrderedDict 的od.popitem(last=True),其保证会返回最右边(最后)的项。常规的 dict 可以通过
(k := next(iter(d)), d.pop(k))来模拟 OrderedDict 的od.popitem(last=False),它将返回并移除最左边(开头)的条目,如果条目存在的话。 -
OrderedDict 类有一个
move_to_end()方法,可以有效地将元素移动到任一端。常规的 dict 可以通过
d[k] = d.pop(k)来模拟 OrderedDict 的od.move_to_end(k, last=True),它将把键及其所关联的值移到最右边(末尾)的位置。常规的 dict 没有 OrderedDict 的
od.move_to_end(k, last=False)的高效等价物,它会把键及其所关联的值移到最左边(开头)的位置。
collections.OrderedDict([items])
返回一个 dict 子类的实例,它具有专门用于重新排列字典顺序的方法。
Added in version 3.1.
popitem(last=True)
有序字典的 popitem() 方法移除并返回一个 (key, value) 键值对。 如果 last 值为真,则按 LIFO 后进先出的顺序返回键值对,否则就按 FIFO 先进先出的顺序返回键值对。
move_to_end(key, last=True)
将一个现有的 key 移到序字典的任一端。 如果 last 为真值(默认)则将条目移到右端,或者如果 last 为假值则将条目移到开头。 如果 key 不存在则会引发 KeyError:
相对于通常的映射方法,有序字典还另外提供了逆序迭代的支持,通过 reversed() 。
OrderedDict 对象之间的相等性检测对顺序敏感并且大致等价于 list(od1.items())==list(od2.items())。
例子和用法
创建记住键值 最后 插入顺序的有序字典变体很简单。 如果新条目覆盖现有条目,则原始插入位置将更改并移至末尾:
class LastUpdatedOrderedDict(OrderedDict):
'Store items in the order the keys were last added'
def __setitem__(self, key, value):
super().__setitem__(key, value)
self.move_to_end(key)
一个 OrderedDict 对于实现 functools.lru_cache() 的变体也很有用:
from collections import OrderedDict
from time import time
class TimeBoundedLRU:
"LRU Cache that invalidates and refreshes old entries."
def __init__(self, func, maxsize=128, maxage=30):
self.cache = OrderedDict() # { args : (timestamp, result)}
self.func = func
self.maxsize = maxsize
self.maxage = maxage
def __call__(self, *args):
if args in self.cache:
self.cache.move_to_end(args)
timestamp, result = self.cache[args]
if time() - timestamp <= self.maxage:
return result
result = self.func(*args)
self.cache[args] = time(), result
if len(self.cache) > self.maxsize:
self.cache.popitem(last=False)
return result
class MultiHitLRUCache:
""" LRU cache that defers caching a result until
it has been requested multiple times.
To avoid flushing the LRU cache with one-time requests,
we don't cache until a request has been made more than once.
"""
def __init__(self, func, maxsize=128, maxrequests=4096, cache_after=1):
self.requests = OrderedDict() # { uncached_key : request_count }
self.cache = OrderedDict() # { cached_key : function_result }
self.func = func
self.maxrequests = maxrequests # 未缓存请求的最大数量
self.maxsize = maxsize # 已存储返回值的最大数量
self.cache_after = cache_after
def __call__(self, *args):
if args in self.cache:
self.cache.move_to_end(args)
return self.cache[args]
result = self.func(*args)
self.requests[args] = self.requests.get(args, 0) + 1
if self.requests[args] <= self.cache_after:
self.requests.move_to_end(args)
if len(self.requests) > self.maxrequests:
self.requests.popitem(last=False)
else:
self.requests.pop(args, None)
self.cache[args] = result
if len(self.cache) > self.maxsize:
self.cache.popitem(last=False)
return result
6.UserDict对象
UserDict 类是用作字典对象的外包装。对这个类的需求已部分由直接创建 dict 的子类的功能所替代;不过,这个类处理起来更容易,因为底层的字典可以作为属性来访问。
class collections.UserDict([initialdata])
模拟字典的类。 这个实例的内容保存在一个常规字典中,它可以通过 UserDict 实例的 data 属性来访问。 如果提供了 initialdata,则 data 会用其内容来初始化;请注意对 initialdata 的引用将不会被保留,以允许它被用于其他目的。
UserDict 实例提供了以下属性作为扩展方法和操作的支持:
data
一个真实的字典,用于保存 UserDict 类的内容。
7.1 UserList
这个类封装了列表对象。它是一个有用的基础类,对于你想自定义的类似列表的类,可以继承和覆盖现有的方法,也可以添加新的方法。这样我们可以对列表添加新的行为。
对这个类的需求已部分由直接创建 list 的子类的功能所替代;不过,这个类处理起来更容易,因为底层的列表可以作为属性来访问。
class collections.UserList([list])
模拟一个列表。这个实例的内容被保存为一个正常列表,通过 UserList 的 data 属性存取。实例内容被初始化为一个 list 的copy,默认为 [] 空列表。 list 可以是迭代对象,比如一个Python列表,或者一个 UserList 对象。
UserList 提供了以下属性作为可变序列的方法和操作的扩展:
data
子类化的要求: UserList 的子类需要提供一个构造器,可以无参数调用,或者一个参数调用。返回一个新序列的列表操作需要创建一个实现类的实例。它假定了构造器可以以一个参数进行调用,这个参数是一个序列对象,作为数据源。
如果一个分离的类不希望依照这个需求,所有的特殊方法就必须重写;请参照源代码进行修改。
8.1UserString
UserString 类是用作字符串对象的外包装。对这个类的需求已部分由直接创建 str 的子类的功能所替代;不过,这个类处理起来更容易,因为底层的字符串可以作为属性来访问。
class collections.UserString(seq)
模拟一个字符串对象。这个实例对象的内容保存为一个正常字符串,通过 UserString 的 data 属性存取。实例内容初始化设置为 seq 的copy。seq 参数可以是任何可通过内建 str() 函数转换为字符串的对象。
330





