1. 别再写 for i in range(len()-1) 了,试试 pairwise
如果你和我一样,在LeetCode或者日常写Python时,经常需要处理一个序列里相邻两个元素的关系,那你一定写过这样的代码:
nums = [1, 2, 3, 4, 5]
for i in range(len(nums) - 1):
current = nums[i]
next_elem = nums[i + 1]
# 然后对 current 和 next_elem 做一些操作...
或者,为了更“Pythonic”一点,可能会用 zip:
for current, next_elem in zip(nums, nums[1:]):
# 操作...
这两种写法都没错,但前者略显啰嗦,后者在列表很长时会创建一个 nums[1:] 的切片副本,虽然通常影响不大,但在追求极致或者处理海量数据时,总感觉不够优雅和高效。这时候,就该 itertools.pairwise 登场了。这个从 Python 3.10 开始加入标准库的小工具,简直就是为这种“滑动窗口”式处理相邻元素而生的。我第一次在刷题时用它替换掉冗长的索引循环后,感觉代码瞬间清爽了一个档次,可读性也大大提升。
简单来说,pairwise 会接收一个可迭代对象(比如列表、字符串、元组,甚至是生成器),然后返回一个迭代器,这个迭代器会依次产出输入中连续的重叠对。用官方文档的例子最直观:pairwise('ABCDEFG') 会依次产出 ('A', 'B'), ('B', 'C'), ('C', 'D')…… 一直到 ('F', 'G')。看到没?它自动帮你把“当前元素”和“下一个元素”打包好了,你直接在一个 for 循环里解包就能用,完全不用操心索引越界或者手动切片。
我刚开始用的时候也犯过嘀咕:这玩意儿不就是 zip(iterable, iterable[1:]) 的封装吗?用起来能有多大区别?但实际用下来,尤其是在算法题这种对边界条件和性能有微妙要求的场景里,我发现它的优势不止一点半点。首先,它处理的是迭代器,是“惰性”的,不会像 nums[1:] 那样产生额外的内存开销,对于超大流式数据非常友好。其次,它的语义极其清晰,for x, y in pairwise(data): 一眼就能看出这是在处理相邻元素对,比 for i in range(len(data)-1): 这种需要脑补索引关系的写法,意图明确太多了。接下来,我们就深入看看怎么把它用到刀刃上。
2. 庖丁解牛:pairwise 的里里外外
2.1 基本用法与核心行为
要用好一个工具,首先得摸清它的脾气。itertools.pairwise(iterable) 的使用非常简单,但有几个关键行为你必须了然于胸,否则很容易踩坑。
第一,输入要求。 它期望一个可迭代对象。什么是可迭代对象?列表、字符串、元组、集合、字典的键、文件对象,或者任何实现了 __iter__ 方法的对象都行。这给了我们极大的灵活性。但有一个重要的边界条件:如果输入的可迭代对象包含的元素少于两个,那么 pairwise 返回的迭代器就是空的。比如 pairwise([1]) 或 pairwise('A'),你用它来迭代,什么也得不到。这是因为至少需要两个元素才能形成一个“对”。这个特性在写通用函数时要特别注意,可能需要先判断长度。
第二,输出特性。 它返回的是一个迭代器,而不是列表。这意味着它是“按需生成”的,非常节省内存。如果你需要查看所有结果,可以用 list() 把它转成列表:list(pairwise([1,2,3,4])) 会得到 [(1,2), (2,3), (3,4)]。注意看,输出的对数总比输入的元素数少一个。输入4个数,得到3个对,这很符合“相邻”的直觉。
让我们看几个具体的例子,巩固一下印象:
from itertools import pairwise
# 例子1:处理列表
data_list = [10, 20, 30, 40]
for a, b in pairwise(data_list):
print(f"差值: {b - a}")
# 输出:
# 差值: 10
# 差值: 10
# 差值: 10
# 例子2:处理字符串(判断是否连续字母)
s = "abcde"
for c1, c2 in pairwise(s):
if ord(c2) - ord(c1) == 1:
print(f"{c1}{c2} 是连续的")
# 输出:
# ab 是连续的
# bc 是连续的
# cd 是连续的
# de 是连续的
# 例子3:处理生成器(无限序列的局部)
def count_up():
n = 0
while True:
yield n
n += 1
# 取前5个相邻对
import itertools
for a, b in itertools.islice(pairwise(count_up()), 5):
print(a, b)
# 输出:
# 0 1
# 1 2
# 2 3
# 3 4
# 4 5
第三个例子展示了 pairwise 与无限生成器配合的能力,这在某些数学计算或流处理场景中很有用,当然要记得用 itertools.islice 之类的工具来限制输出,不然循环就停不下来了。
2.2 自己动手实现一个 pairwise
理解一个函数最好的方式,就是尝试自己实现它。Python官方文档里其实给出了 pairwise 的一个等价实现,我们一起来拆解一下,这对理解迭代器的工作机制大有裨益:
def my_pairwise(iterable):
# 步骤1:获取输入可迭代对象的迭代器
iterator = iter(iterable)
# 步骤2:取出第一个元素,如果为空则用None填充
a = next(iterator, None)
# 步骤3:遍历剩下的元


1433

被折叠的 条评论
为什么被折叠?



