关于python:来自单个列表的pairs

Pairs from single list

通常,我发现需要成对处理一个列表。我想知道哪一种方法是Python式的和有效的,并在谷歌上发现了这一点:

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

我认为那是Python式的,但在最近一次关于习语和效率的讨论之后,我决定做一些测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
2
3
4
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实现列表、列表索引和列表切片是非常有效的。这是一个既令人欣慰又出人意料的结果。

有没有另一种"更好"的成对遍历列表的方法?

请注意,如果列表中元素的数目是奇数,那么最后一个元素将不在任何对中。

哪种方法是确保所有元素都包含在内的正确方法?

我从测试的答案中添加了这两个建议:

1
2
3
4
5
6
7
def pairwise(t):
    it = iter(t)
    return izip(it, it)

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

结果如下:

1
2
0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

迄今为止的结果

最有毒气和效率的:

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

最有效和最刺激的Python:

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

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

为了处理元素数目为奇数的序列,建议增加原始序列,添加一个元素(None)与上一个元素配对,这是可以用itertools.izip_longest()实现的。

终于

注意,在python 3.x中,zip()的行为与itertools.izip()的行为相同,itertools.izip()已不复存在。


我最喜欢的方法是:

1
2
3
4
5
6
7
8
9
10
from itertools import izip

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

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

当您想要配对所有元素时,显然可能需要一个fillValue:

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


我认为您的初始解决方案pairs = zip(t[::2], t[1::2])是最好的,因为它最容易读取(在python 3中,zip自动返回迭代器而不是列表)。

为了确保包含所有元素,您可以简单地通过None扩展列表。

然后,如果列表中元素的数目为奇数,则最后一对将是(item, None)

1
2
3
4
5
6
7
8
>>> 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)]


我从小小的免责声明开始-不要使用下面的代码。根本不是Python,我只是为了好玩才写的。它类似于@thc4k pairwise函数,但它使用iterlambda闭包。它不使用itertools模块,不支持fillvalue模块。我把它放在这里是因为有人可能会觉得它很有趣:

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

就大多数的Python而言,我认为在python源文档中提供的食谱(其中一些看起来很像@jochenritzel提供的答案)可能是你最好的选择;)

1
2
3
4
5
def grouper(iterable, n, fillvalue=None):
   "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

Is there another,"better" way of traversing a list in pairs?

我不能肯定,但我对此表示怀疑:任何其他的遍历都将包含更多必须解释的Python代码。像zip()这样的内置函数是用C编写的,这要快得多。

Which would be the right way to ensure that all elements are included?

检查列表的长度,如果是奇数(len(list) & 1 == 1),复制列表并附加一个项目。


1
2
3
4
5
6
7
8
>>> my_list = [1,2,3,4,5,6,7,8,9,10]
>>> my_pairs = list()
>>> while(my_list):
...     a = my_list.pop(0); b = my_list.pop(0)
...     my_pairs.append((a,b))
...
>>> print(my_pairs)
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]