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


131

有时我需要在Python中迭代一个列表,以查看“当前”元素和“下一个”元素。到目前为止,我已经使用以下代码完成了此操作:

for current, next in zip(the_list, the_list[1:]):
    # Do something

这行得通,符合我的期望,但是有没有一种更惯用或有效的方式来执行相同的操作?


检查MizardX答案是否有此问题。但是我不认为这种解决方案比您更惯用。
法比奥·迪尼兹(FábioDiniz)


39
既然没有人提到过它,那么我将成为那个家伙,并指出使用next这种方式掩盖了内置函数。
senderle 2011年

@senderle也许是Python 2…
Quintec

2
@ thecoder16:next也内置在Python 2.功能
zondo

Answers:


131

这是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)   

对于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)

工作原理:

首先,创建两个并行的迭代器,a并将b它们(tee()称为调用)都指向原始可迭代的第一个元素。第二个迭代器b向前移动1个(next(b, None))调用。此时指向as0并b指向s1。双方ab可以独立遍历原来迭代器-的izip函数接受两个迭代器,使对返回的元素,以相同的速度前进的两个迭代器。

一个警告:该tee()函数产生两个可以彼此独立进行的迭代器,但这要付出一定的代价。如果一个迭代器比另一个迭代器前进得更多,则tee() 需要将消耗的元素保留在内存中,直到第二个迭代器也将它们消耗掉为止(它不能“倒回”原始迭代器)。这里没有关系,因为一个迭代器仅比另一个迭代器领先1步,但是通常很容易以这种方式使用大量内存。

而且由于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)

4
该示例代码很棒。但是,您能否对其作一点解释呢?就像说“ tee()”和“ next()”在这里做什么。
约翰·穆尔德

@约翰·穆尔德:做了简短的总结。
2011年

9
zip(ł, ł[1:])更短更Python
noɥʇʎԀʎzɐɹƆ

2
@noɥʇʎԀʎzɐɹƆ:不,它不适用于所有可迭代项,并且在列表上使用时会产生不必要的复制。使用函数是pythonic。
Ry-

此功能在funcy模块中实现funcy.pairwise:: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR,

30

自己滚!

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

    for b in it:
        yield (a, b)
        a = b

1
正是我所需要的!这是作为python方法而永生的,还是我们需要继续滚动?
uhoh

1
@uhoh:据我所知还没有!
Ry-

21

由于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)

根本不会复制列表。


3
请注意,在python 3.x中,izip被itertools抑制了,您应该使用内置的zip
Xavier Combelle

1
实际上,the_list[1:]不仅创建切片对象而不是创建几乎整个列表的副本,因此OP的技术并没有听起来那么浪费。
martineau 2011年

3
我认为[1:]创建切片对象(或者可能是“ 1:”),该对象将传递到__slice__列表中,然后返回仅包含所选元素的副本。复制列表的一种惯用方式是l_copy = l[:](我觉得这很丑陋且l_copy = list(l)
难以理解

4
@dcrosta:没有__slice__特殊方法。 the_list[1:]等效于the_list[slice(1, None)],而等效于list.__getitem__(the_list, slice(1, None))
Sven Marnach 2011年

4
@martineau:创建的副本the_list[1:]只是一个浅表副本,因此每个列表项仅包含一个指针。内存消耗更大的部分zip()本身就是它,因为它将为tuple每个列表项创建一个实例列表,每个实例将包含指向这两个项的两个指针以及一些其他信息。此列表将消耗由副本引起的副本所[1:]消耗的内存量的九倍。
Sven Marnach 2011年

19

我只是将其列出来,我很惊讶没有人想到enumerate()。

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

11
实际上,if如果使用切片,也可以将其删除:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
生命平衡

2
这确实是答案,它不依赖任何额外的导入并且效果很好。
jamescampbell

但是,它不适用于不可索引的迭代器,因此它不是通用解决方案。
维姆

14

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

#!/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)

输出:

(1, 2)
(2, 3)
(3, 4)

您的答案更多是先前当前问题,而不是当前下一个问题。我进行了修改,以改善语义,因此它i始终是当前元素的索引。
Bengt 2012年

1

截至2020年5月16日,这现在是一个简单的导入

from more_itertools import pairwise
for current, next in pairwise(your_iterable):
  print(f'Current = {current}, next = {nxt}')

更多itertools的文档 该代码与其他答案中的代码相同,但我更喜欢在可用时导入。

如果尚未安装,则: pip install more-itertools

例如,如果您有fibbonnacci序列,则可以计算后续对的比率为:

from more_itertools import pairwise
fib= [1,1,2,3,5,8,13]
for current, nxt in pairwise(fib):
    ratio=current/nxt
    print(f'Curent = {current}, next = {nxt}, ratio = {ratio} ')

0

使用列表理解从列表中配对

the_list = [1, 2, 3, 4]
pairs = [[the_list[i], the_list[i + 1]] for i in range(len(the_list) - 1)]
for [current_item, next_item] in pairs:
    print(current_item, next_item)

输出:

(1, 2)
(2, 3)
(3, 4)

0

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

Python 3:

from itertools import islice

def n_wise(iterable, n):
    return zip(*(islice(iterable, i, None) for i in range(n)))

Python 2:

from itertools import izip, islice

def n_wise(iterable, n):
    return izip(*(islice(iterable, i, None) for i in xrange(n)))

它可以通过进行成对迭代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 !

-2

基本解决方案:

def neighbors( list ):
  i = 0
  while i + 1 < len( list ):
    yield ( list[ i ], list[ i + 1 ] )
    i += 1

for ( x, y ) in neighbors( list ):
  print( x, y )

-2
code = '0016364ee0942aa7cc04a8189ef3'
# Getting the current and next item
print  [code[idx]+code[idx+1] for idx in range(len(code)-1)]
# Getting the pair
print  [code[idx*2]+code[idx*2+1] for idx in range(len(code)/2)]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.