在Python中循环遍历列表中的一对值(循环方式)

37

问题很简单,我想要遍历列表中的每个元素和它后面的元素(将最后一个元素与第一个元素组成一对)。

我想到了两种非Pythonic的方法:

def pairs(lst):
    n = len(lst)
    for i in range(n):
        yield lst[i],lst[(i+1)%n]

并且:

def pairs(lst):
    return zip(lst,lst[1:]+lst[:1])

期望输出:

>>> for i in pairs(range(10)):
    print i

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

您有没有更 Pythonic 的方式来做这件事?也许有一些我没听说过的预定义函数可以使用?

另外,一个更一般的 n-fold 版本(三元组、四元组等而不是对)可能也很有趣。


1
你的第一个解决方案已经足够好了! - Xolve
3
在第二个版本中,将lst[0]更改为lst[:1]以使其适用于空序列。代码变得更加对称。 - Darius Bacon
1
在第二个版本中,+[lst[:1]] 应该被替换为 +lst[:1],是吗? - Manuel Selva
@ManuelSelva 您是正确的,当我写下这个代码时它是 [lst[0]],但我不知道为什么 @colonelpanic 把它改了。 - fortran
13个回答

31
def pairs(lst):
    i = iter(lst)
    first = prev = item = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield item, first

适用于任何非空序列,不需要进行索引操作。


1
我喜欢它的流媒体质量。而且使用iter()很精细。 - hughdbrown
7
如果需要支持空序列,请使用next(i,sentinel)代替过时的i.next(),在设置sentinel = object()之后; 在for循环之前加入if first is sentinel: return - Alex Martelli
@Darius:我已经澄清了序列必须是非空的。对于一个空序列,它应该做什么是有争议的:原始问题的版本1生成一个空序列,而版本2会引发IndexError异常。 - Martin v. Löwis
2
在一个只有1个元素的序列上崩溃:在调用i.next()之后,序列变为空,因此循环不会绑定item,所以yield item, first会引发UnboundLocalError - Katriel
1
对于 Python 3:将 i.next() 替换为 next(i) 即可。而且它在 Python 2 上也可以正常工作。 - j-i-l

9

我已经编写了元组的一般版本,我喜欢第一个版本,因为它简洁优雅,越看越像Python... 毕竟,有什么比使用zip、星号参数扩展、列表推导式、列表切片、列表连接和"range"更Pythonic的呢?

def ntuples(lst, n):
    return zip(*[lst[i:]+lst[:i] for i in range(n)])

即使处理大型列表,使用itertools版本也足够高效...

from itertools import *
def ntuples(lst, n):
    return izip(*[chain(islice(lst,i,None), islice(lst,None,i)) for i in range(n)])

还有一个适用于不可索引序列的版本:

from itertools import *
def ntuples(seq, n):
    iseq = iter(seq)
    curr = head = tuple(islice(iseq, n))
    for x in chain(iseq, head):
        yield curr
        curr = curr[1:] + (x,)

无论如何,感谢大家的建议!:-)

5
你问题中的第一个答案比这两个答案容易理解一百万倍。在我看来,这让它更符合Pythonic风格。:-/ - John Fouhy
尝试这个代码: "for a in ntuples(count(), 3): print a;" 我认为你需要使用itertools.tee()使其正常运行。 - hughdbrown
“count”不是一个可索引的序列,因此使用切片方法并不能正常工作。 - fortran
哦,一定要爱讽刺! - Humphrey Bogart

6

我一如既往地喜欢T恤:

from itertools import tee, izip, chain

def pairs(iterable):
    a, b = tee(iterable)
    return izip(a, chain(b, [next(b)]))

5
这可能是令人满意的:
def pairs(lst):
    for i in range(1, len(lst)):
        yield lst[i-1], lst[i]
    yield lst[-1], lst[0]

>>> a = list(range(5))
>>> for a1, a2 in pairs(a):
...     print a1, a2
...
0 1
1 2
2 3
3 4
4 0

如果您喜欢这类的内容,请查看关于Python的文章,可以在wordaligned.org 上找到。作者特别钟爱Python中的生成器技术。

是的,我注意到了并且已经修复了它——只用了下降投票所需的时间。这是我的错,真的。 - hughdbrown

2
我会这样做(主要是因为我能读懂):
class Pairs(object):
    def __init__(self, start):
        self.i = start
    def next(self):
        p, p1 = self.i, self.i + 1
        self.i = p1
        return p, p1
    def __iter__(self):
        return self

if __name__ == "__main__":
    x = Pairs(0)
    y = 1
    while y < 20:
        print x.next()
        y += 1

提供:

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

+1 - 就我个人而言,我更喜欢这种解决方案,而不是基于“yield”的解决方案。 - Tall Jeff

1
[(i,(i+1)%len(range(10))) for i in range(10)]

用你想要的列表替换range(10)。

通常在Python中,“循环索引”非常容易;只需使用:

a[i%len(a)] 

0
这里有一个版本支持可选的起始索引(例如,要返回(4,0)作为第一对,请使用start = -1:)。
import itertools

def iterrot(lst, start = 0):

    if start == 0:
        i = iter(lst)
    elif start > 0:
        i1 = itertools.islice(lst, start, None)
        i2 = itertools.islice(lst, None, start)
        i = itertools.chain(i1, i2)
    else:
        # islice doesn't support negative slice indices so...
        lenl = len(lst)
        i1 = itertools.islice(lst, lenl + start, None)
        i2 = itertools.islice(lst, None, lenl + start)
        i = itertools.chain(i1, i2)
    return i


def iterpairs(lst, start = 0):

    i = iterrot(lst, start)     

    first = prev = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield prev, first


def itertrios(lst, start = 0):

    i = iterrot(lst, start)     

    first = prevprev = i.next()
    second = prev = i.next()
    for item in i:
        yield prevprev, prev, item
        prevprev, prev = prev, item

    yield prevprev, prev, first
    yield prev, first, second

0

关于解决一般情况的问题,我的回答如下:

import itertools

def pair(series, n):
    s = list(itertools.tee(series, n))
    try:
        [ s[i].next() for i in range(1, n) for j in range(i)]
    except StopIteration:
        pass
    while True:
        result = []
        try:
            for j, ss in enumerate(s):
                result.append(ss.next())
        except StopIteration:
            if j == 0:
                break
            else:
                s[j] = iter(series)
                for ss in s[j:]:
                    result.append(ss.next())
        yield result

输出结果如下:

>>> for a in pair(range(10), 2):
...     print a
...
[0, 1]
[1, 2]
[2, 3]
[3, 4]
[4, 5]
[5, 6]
[6, 7]
[7, 8]
[8, 9]
[9, 0]
>>> for a in pair(range(10), 3):
...     print a
...
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 0]
[9, 0, 1]

现在,pair() 的命名不准确了 ;) - Stefano Borini
是的,名称不正确,不太美观,而且在OP自己的Python答案之后2分钟。嗯,好吧。 - hughdbrown

0

这个过程会无限循环,无论是好是坏,但在算法上非常清晰。

from itertools import tee, cycle

def nextn(iterable,n=2):
    ''' generator that yields a tuple of the next n items in iterable.
    This generator cycles infinitely '''
    cycled = cycle(iterable)
    gens = tee(cycled,n)

    # advance the iterators, this is O(n^2)
    for (ii,g) in zip(xrange(n),gens):
        for jj in xrange(ii):
            gens[ii].next()

    while True:
        yield tuple([x.next() for x in gens])


def test():
    data = ((range(10),2),
        (range(5),3),
        (list("abcdef"),4),)
    for (iterable, n) in data:
        gen = nextn(iterable,n)
        for j in range(len(iterable)+n):
            print gen.next()            


test()

给出:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
(9, 0)
(0, 1)
(1, 2)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, 0)
(4, 0, 1)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')
('e', 'f', 'a', 'b')
('f', 'a', 'b', 'c')
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')

我喜欢使用itertools.cycle()。我不确定你的测试代码为什么要用range(len(iter) + n)。我认为range(len(iter))是正确的。你的n**2代码用于推进迭代器,就像我的代码:[s[i].next() for i in range(1, n) for j in range(i)]。 - hughdbrown
关于 len(iter)+n... 我想要展示它确实会无限循环。毕竟这只是测试代码。你的一行代码用于进阶更短/更好。无论如何,那是代码中绝对需要注释的一行,因为它是算法的关键 :) - Gregg Lind

0

Fortran的zip * range解决方案的更短版本(这次使用lambda):

group = lambda t, n: zip(*[t[i::n] for i in range(n)])

group([1, 2, 3, 3], 2)

给出:

[(1, 2), (3, 4)]

不错,但还不太对。我需要成对迭代,但要重复元素并进行包装。以你的例子为例,输出应该是[(1,2),(2,3),(3,4),(4,1)] - fortran

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