从单个列表中获取配对

124 投票
10 回答
90933 浏览
提问于 2025-04-16 09:34

我经常需要按对来处理一个列表。我在想用什么样的方式在Python中做这件事比较好,结果在谷歌上找到了这个:

pairs = zip(t[::2], t[1::2])

我觉得这个方法挺符合Python风格的,但在最近一次讨论中,涉及到了一些关于习惯用法和效率的内容,所以我决定做一些测试:

import time
from itertools import islice, izip

def pairs_1(t):
    return zip(t[::2], t[1::2]) 

def pairs_2(t):
    return izip(t[::2], t[1::2]) 

def pairs_3(t):
    return izip(islice(t,None,None,2), islice(t,1,None,2))

A = range(10000)
B = xrange(len(A))

def pairs_4(t):
    # ignore value of t!
    t = B
    return izip(islice(t,None,None,2), islice(t,1,None,2))

for f in pairs_1, pairs_2, pairs_3, pairs_4:
    # time the pairing
    s = time.time()
    for i in range(1000):
        p = f(A)
    t1 = time.time() - s

    # time using the pairs
    s = time.time()
    for i in range(1000):
        p = f(A)
        for a, b in p:
            pass
    t2 = time.time() - s
    print t1, t2, t2-t1

这是我在电脑上得到的结果:

1.48668909073 2.63187503815 1.14518594742
0.105381965637 1.35109519958 1.24571323395
0.00257992744446 1.46182489395 1.45924496651
0.00251388549805 1.70076990128 1.69825601578

如果我理解得没错,这意味着Python中列表的实现、索引和切片都非常高效。这让我感到既安心又意外。

有没有其他“更好”的方法来按对遍历一个列表呢?

需要注意的是,如果列表的元素个数是奇数,那么最后一个元素将不会被包含在任何一对中。

怎样才能确保所有元素都被包含呢?

我把这些建议从答案中加入到测试中:

def pairwise(t):
    it = iter(t)
    return izip(it, it)

def chunkwise(t, size=2):
    it = iter(t)
    return izip(*[it]*size)

这是结果:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

到目前为止的结果

最符合Python风格且非常高效:

pairs = izip(t[::2], t[1::2])

最有效且也很符合Python风格:

pairs = izip(*[iter(t)]*2)

我花了一点时间才明白,第一个答案使用了两个迭代器,而第二个只用了一个。

对于元素个数为奇数的序列,建议是增加一个元素(None),这个元素会和之前的最后一个元素配对,这可以通过使用itertools.izip_longest()来实现。

最后

需要注意的是,在Python 3.x中,zip()的行为就像itertools.izip(),而itertools.izip()已经不再使用了。

10 个回答

6

我先说个小声明 - 不要使用下面的代码。这段代码一点也不符合Python的风格,我只是为了好玩写的。它和@THC4k的pairwise函数有点像,但它使用了iterlambda闭包。它没有使用itertools模块,也不支持fillvalue。我把它放在这里是因为可能有人会觉得有趣:

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)
55

我觉得你最开始的解决方案 pairs = zip(t[::2], t[1::2]) 是最好的,因为它最容易理解(而且在Python 3中,zip 会自动返回一个迭代器,而不是列表)。

为了确保所有元素都被包含,你可以简单地在列表末尾加上 None

这样,如果列表的元素个数是奇数,最后一对就会变成 (item, None)

>>> t = [1,2,3,4,5]
>>> t.append(None)
>>> zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, None)]
>>> t = [1,2,3,4,5,6]
>>> t.append(None)
>>> zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, 6)]
66

我最喜欢的做法是:

def pairwise(t):
    it = iter(t)
    return zip(it,it)

# for "pairs" of any length
def chunkwise(t, size=2):
    it = iter(t)
    return zip(*[it]*size)

当你想要把所有元素配对的时候,显然你可能需要一个填充值:

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)

在Python 3中,itertools.izip 现在简单地变成了 zip .. 如果你在使用旧版的Python,可以用:

from itertools import izip as zip

撰写回答