在Python中将列表迭代为对(当前,下一个)

dcr*_*sta 121 python

我有时需要在Python中迭代一个列表,查看"current"元素和"next"元素.到目前为止,我已经完成了以下代码:

for current, next in zip(the_list, the_list[1:]):
    # Do something
Run Code Online (Sandbox Code Playgroud)

这有效并且符合我的预期,但是有更多惯用或有效的方法来做同样的事情吗?

Raf*_*ird 121

以下是itertools模块文档中的相关示例:

import itertools
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)   
Run Code Online (Sandbox Code Playgroud)

对于Python 2,您需要itertools.izip而不是zip:

import itertools
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)
Run Code Online (Sandbox Code Playgroud)

这是如何工作的:

首先,两个平行的迭代器,ab被创建(在tee()呼叫),两者都指向原始迭代的第一个元素.第二个迭代器,b向前移动一步(next(b, None))调用).此时指向as0并b指向s1.双方ab可以独立遍历原来迭代器-的izip函数接受两个迭代器,使对返回的元素,以相同的速度前进的两个迭代器.

一个警告:该tee()函数产生两个迭代器,它们可以相互独立地前进,但它需要付出代价.如果其中一个迭代器比另一个迭代器进一步前进,那么tee() 需要将消耗的元素保留在内存中,直到第二个迭代器也将它们联合起来(它不能"回放"原始迭代器).这里没关系,因为一个迭代器只比另一个迭代器领先一步,但一般来说这很容易以这种方式使用大量内存.

既然tee()可以使用n参数,这也可以用于两个以上的并行迭代器:

def threes(iterator):
    "s -> (s0,s1,s2), (s1,s2,s3), (s2, s3,4), ..."
    a, b, c = itertools.tee(iterator, 3)
    next(b, None)
    next(c, None)
    next(c, None)
    return zip(a, b, c)
Run Code Online (Sandbox Code Playgroud)

  • `zip(ł,ł[1:])`更短更pythonic (8认同)
  • 注意:从 3.10 开始,[`pairwise` 直接在 `itertools`](https://docs.python.org/3/library/itertools.html#itertools.pairwise) 中提供(相当于 `pairwise` 配方,但完全推到了 C 层,使其在 CPython 参考解释器上速度更快)。 (7认同)
  • 示例代码很棒......但是,您能否解释一下它的工作原理?比如说"tee()"和"next()"在这里做什么. (3认同)
  • @noɥʇʎԀʎzɐɹƆ:不,它不适用于所有可迭代项,并且在列表上使用时会产生不必要的复制。使用函数是pythonic。 (2认同)

Ry-*_*Ry- 26

滚动你自己!

def pairwise(iterable):
    it = iter(iterable)
    a = next(it, None)

    for b in it:
        yield (a, b)
        a = b
Run Code Online (Sandbox Code Playgroud)

  • 正是我所需要的!这是作为python方法永垂不朽的,还是我们需要继续滚动? (2认同)
  • @uhoh:据我所知还没有! (2认同)
  • 它将很快作为 [`itertools.pairwise`](https://docs.python.org/3.10/library/itertools.html#itertools.pairwise) 包含在 3.10 中! (2认同)

Sve*_*ach 19

由于the_list[1:]实际上创建了整个列表的副本(不包括其第一个元素),并zip()在调用时立即创建元组列表,因此总共创建了三个列表副本.如果您的列表非常大,您可能更喜欢

from itertools import izip, islice
for current_item, next_item in izip(the_list, islice(the_list, 1, None)):
    print(current_item, next_item)
Run Code Online (Sandbox Code Playgroud)

根本不复制列表.

  • @dcrosta:没有`__slice__`特殊方法.`the_list [1:]`相当于`the_list [slice(1,None)]`,它相当于`list .__ getitem __(the_list,slice(1,None))`. (4认同)
  • @martineau:``__ [1:]`创建的副本只是一个浅拷贝,所以每个列表项只包含一个指针.内存密集度较高的部分是`zip()`本身,因为它将为每个列表项创建一个`tuple`实例的列表,每个实例将包含两个指向这两个项的指针和一些附加信息.此列表将消耗由"[1:]"消耗的副本的内存量的九倍. (4认同)
  • 请注意,在python 3.x中,izip被抑制为itertools,你应该使用builtin zip (3认同)
  • 我认为`[1:]`创建切片对象(或者可能是"1:`"),它被传递给列表上的`__slice__`,然后返回一个只包含所选元素的副本.复制列表的一种惯用方法是`l_copy = l [:]`(我发现它很丑陋且不可读 - 更喜欢`l_copy = list(l)`) (3认同)
  • 实际上,`the_list[1:]` 不只是创建一个切片对象而不是几乎整个列表的副本 - 所以OP的技术并不像你听起来那么浪费。 (2认同)

Qui*_*tec 14

我只是把它拿出来,我很惊讶没有人想到枚举().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Run Code Online (Sandbox Code Playgroud)

  • 实际上,如果你使用切片,也可以删除`if`:`for(index,thing)in enumerate(the_list [: - 1]):current,next_ = thing,the_list [index + 1]` (7认同)
  • 这确实是答案,它不依赖任何额外的导入并且效果很好。 (2认同)
  • 不过,它不适用于不可索引的迭代,因此它不是通用的解决方案。 (2认同)

Rum*_*kin 13

通过索引迭代可以做同样的事情:

#!/usr/bin/python
the_list = [1, 2, 3, 4]
for i in xrange(len(the_list) - 1):
    current_item, next_item = the_list[i], the_list[i + 1]
    print(current_item, next_item)
Run Code Online (Sandbox Code Playgroud)

输出:

(1, 2)
(2, 3)
(3, 4)
Run Code Online (Sandbox Code Playgroud)


Xav*_*hot 11

在即将到来的Python 3.10 release schedule,这是该pairwise功能的确切作用:

from itertools import pairwise

list(pairwise([1, 2, 3, 4, 5]))
# [(1, 2), (2, 3), (3, 4), (4, 5)]
Run Code Online (Sandbox Code Playgroud)

或者只是pairwise([1, 2, 3, 4, 5])如果您不需要结果作为list.


Mar*_*lli 5

我真的很惊讶没有人提到更短、更简单且最重要的通用解决方案:

Python 3:

from itertools import islice

def n_wise(iterable, n):
    return zip(*(islice(iterable, i, None) for i in range(n)))
Run Code Online (Sandbox Code Playgroud)

Python 2:

from itertools import izip, islice

def n_wise(iterable, n):
    return izip(*(islice(iterable, i, None) for i in xrange(n)))
Run Code Online (Sandbox Code Playgroud)

它通过传递 来进行成对迭代n=2,但可以处理任何更高的数字:

>>> for a, b in n_wise('Hello!', 2):
>>>     print(a, b)
H e
e l
l l
l o
o !

>>> for a, b, c, d in n_wise('Hello World!', 4):
>>>     print(a, b, c, d)
H e l l
e l l o
l l o
l o   W
o   W o
  W o r
W o r l
o r l d
r l d !
Run Code Online (Sandbox Code Playgroud)