在列表中遍历所有相邻项的对。

123
给定一个列表。
l = [1, 7, 3, 5]

我想遍历所有连续列表项的一对,即(1,7), (7,3), (3,5)

for i in xrange(len(l) - 1):
    x = l[i]
    y = l[i + 1]
    # do something

我希望以更紧凑的方式实现这一点,例如

for x, y in someiterator(l): ...

有没有一种使用Python内置迭代器来完成这个任务的方法?我相信itertools模块应该有解决方案,但我就是想不出来。


3
虽然我接受了sberry的答案,因为我要求一个简单的内置解决方案,但也请考虑thefourtheye和HansZauber提供的优雅且更高效的解决方案。 - flonk
7个回答

196

只需使用zip

>>> l = [1, 7, 3, 5]
>>> for first, second in zip(l, l[1:]):
...     print(first, second)
...
1 7
7 3
3 5

如果您使用Python 2(不建议),您可以考虑在非常长的列表中使用itertools中的izip函数,以避免创建新的列表。
import itertools

for first, second in itertools.izip(l, l[1:]):
    ...

6
在Python 2中,zip()会返回一个新的列表。最好使用itertools.izip() - Tim Pietzcker
80
在Python 3中,zip()函数返回一个迭代器。最好使用Python 3。 - Noctua
6
谢谢,但应该是zip(l[:-1], l[1:])而不是zip(l, l[1:]),对吗? - flonk
10
这会创建一个 l 的副本(几乎所有的元素),但没有任何理由。 - Bach
4
@flonk,并不是所有情况下都会使用zip来尝试生成完整的组。 - sberry
显示剩余3条评论

44

看看itertools recipes中的pairwise: http://docs.python.org/2/library/itertools.html#recipes

引用自那里:

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

通用版本

通用版本可以生成任何给定正整数大小的元组,可能如下所示:

def nwise(iterable, n=2):                                                      
    iters = tee(iterable, n)                                                     
    for i, it in enumerate(iters):                                               
        next(islice(it, i, i), None)                                               
    return izip(*iters)   

2
我喜欢这种方法,因为它不会复制输入的可迭代对象。对于Python3,只需使用 zip 而不是 izip - normanius
如何扩展以包括(sLast,s0)?因此,不是产生n-1对,而是返回n对? - normanius
1
@normanius 我认为最简单的扩展方法就是在iterable的末尾填充其开头相关值的副本:nwise(chain(a, islice(b, n-1)), n),其中 a, b = tee(iterable) - Ryan Tarpine
对于Python 3:https://docs.python.org/3/library/itertools.html#itertools.pairwise - scorpionipx

14

我会创建一个通用的分组器生成器,像这样

def grouper(input_list, n = 2):
    for i in xrange(len(input_list) - (n - 1)):
        yield input_list[i:i+n]

样例运行 1

for first, second in grouper([1, 7, 3, 5, 6, 8], 2):
    print first, second

输出

1 7
7 3
3 5
5 6
6 8

示例运行1

for first, second, third in grouper([1, 7, 3, 5, 6, 8], 3):
    print first, second, third

输出

1 7 3
7 3 5
3 5 6
5 6 8

你可以像这样编写生成器推导式pair_generator = ((list[i], list[i+1]) for i in range(0, len(list)-1)) - Hieu Doan

4

将sberry的方法推广到使用推导式的nwise:

def nwise(lst, k=2):
    return list(zip(*[lst[i:] for i in range(k)])) 

Eg

nwise(list(range(10)),3)

[(0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6), (5, 6, 7), (6, 7, 8), (7, 8, 9)]

这段代码表示的是一个包含多个元组的列表,每个元组都有三个整数。

3
一种简单的方法是使用一个生成器来存储前一个元素,以避免不必要的复制。
def pairs(iterable):
    """Yield elements pairwise from iterable as (i0, i1), (i1, i2), ..."""
    it = iter(iterable)
    try:
        prev = next(it)
    except StopIteration:
        return
    for item in it:
        yield prev, item
        prev = item

与基于索引的解决方案不同,这个解决方案适用于任何可迭代对象,包括那些不支持索引(例如生成器)或速度较慢(例如collections.deque)的对象。

1
你可以使用一个 zip
>>> list(zip(range(5), range(2, 6)))
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]

就像拉链一样,它会创建一对一对的元素。因此,要将两个列表混合,你需要:
>>> l = [1,7,3,5]
>>> list(zip(l[:-1], l[1:]))
[(1, 7), (7, 3), (3, 5)]

然后迭代就像这样进行。
for x, y in zip(l[:-1], l[1:]):
    pass

1
你不需要修剪第一个的结尾,因为zip只会生成完整的组。如果你使用izip_longest,那么情况就不同了,但是为什么要这样做呢? - sberry
@sberry: 你说得没错,但我更喜欢这样显式表达。可能这是个人的喜好吧。 - Noctua

-2
如果你想要一些内联的东西,但不是非常易读,这里有另一个解决方案,它利用了生成器。我认为从性能上来说也不是最好的 :-/

将列表转换为生成器,并在最后一项之前进行微调:

gen = (x for x in l[:-1])

将其转换为一对:

[(gen.next(), x) for x in l[1:]]

这就是你所需要的。


1
对于l = [1, 2, 3, 4],这将生成[(1, 2), (3, 4)],而不是要求的[(1, 2), (2, 3), (3, 4)]。它仅在列表包含偶数项时起作用。 - David Pärsson
哎呀,你说得对。很抱歉,我不应该在没有测试的情况下在互联网上发布垃圾信息。我已经纠正了它,现在应该可以工作了(希望如此),如果你对这种解决方案感兴趣的话。 - Burak Cetin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接