Iterar uma lista como par (atual, próximo) em Python


131

Às vezes, eu preciso iterar uma lista no Python, olhando para o elemento "atual" e o elemento "próximo". Até agora, fiz isso com código como:

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

Isso funciona e faz o que eu espero, mas há uma maneira mais idiomática ou eficiente de fazer a mesma coisa?


Verifique a resposta do MizardX para esta pergunta . Mas não acho que essa solução seja mais idiomática do que a sua.
Fábio Diniz

2
Dê uma olhada no Build a Basic Python Iterator .
mkluwe

39
já que ninguém mais mencionou isso, eu serei esse cara e ressalto que nextesse método mascara um built-in.
Senderle

@senderle Talvez seja Python 2 ...
Quintec

2
@ thecoder16: nexté também um built-in função em Python 2.
Zondo

Respostas:


131

Aqui está um exemplo relevante dos documentos do módulo 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)   

Para Python 2, você precisa, em itertools.izipvez de 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)

Como isso funciona:

Primeiro, dois iteradores paralelos ae bsão criados (a tee()chamada), ambos apontando para o primeiro elemento do iterável original. O segundo iterador bé movido 1 passo à frente (a next(b, None)) chamada. Nesse ponto, aaponte para s0 e baponte para s1. Ambos ae bpodem atravessar o iterador original independentemente - a função izip pega os dois iteradores e faz pares dos elementos retornados, avançando os dois iteradores no mesmo ritmo.

Uma ressalva: a tee()função produz dois iteradores que podem avançar independentemente um do outro, mas tem um custo. Se um dos iteradores avançar mais que o outro, será tee() necessário manter os elementos consumidos na memória até o segundo iterador consumi-los também (ele não pode 'rebobinar' o iterador original). Aqui não importa, porque um iterador está apenas um passo à frente do outro, mas, em geral, é fácil usar muita memória dessa maneira.

E como tee()pode assumir um nparâmetro, isso também pode ser usado para mais de dois iteradores paralelos:

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
O código de exemplo é ótimo ... mas, você poderia dar uma pequena explicação sobre por que ele funciona? Como dizer o que "tee ()" e "next ()" estão fazendo aqui.
John Mulder

@ John Mulder: Fiz um breve resumo.
Rafał Dowgird 17/07/11

9
zip(ł, ł[1:])é muito mais curto e pythônico
noɥʇʎԀʎzɐɹƆ

2
@ noɥʇʎԀʎzɐɹƆ: Não, ele não funciona em todos os iteráveis ​​e faz uma cópia desnecessária quando usado em listas. O uso de funções é pitônico.
Ry-

Esta função implementada no funcymódulo: funcy.pairwise: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR

30

Roll your own!

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

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

1
Apenas o que eu precisava! Isso foi imortalizado como um método python ou precisamos continuar rolando?
uhoh

1
@uhoh: Ainda não foi tão longe quanto eu sei!
Ry-

21

Como, the_list[1:]na verdade, cria uma cópia da lista inteira (excluindo o primeiro elemento) e zip()cria uma lista de tuplas imediatamente quando chamada, no total, três cópias da sua lista são criadas. Se sua lista é muito grande, você pode preferir

from itertools import izip, islice
for current_item, next_item in izip(the_list, islice(the_list, 1, None)):
    print(current_item, next_item)

que não copia a lista.


3
nota que, em 3.x python izip é suprimida de itertools e você deve usar builtin zip
Xavier Combelle

1
Na verdade, não the_list[1:]apenas cria um objeto de fatia em vez de uma cópia de quase toda a lista - portanto, a técnica do OP não é tão inútil quanto você soa.
27411 martineau

3
Eu acho que [1:]cria o objeto de fatia (ou possivelmente " 1:"), que é passado para __slice__a lista, que retorna uma cópia contendo apenas os elementos selecionados. Uma forma idiomática para copiar uma lista é l_copy = l[:](o que eu acho feia e ilegível - preferir l_copy = list(l))
dcrosta

4
@ dcrosta: Não existe __slice__um método especial. the_list[1:]é equivalente a the_list[slice(1, None)], que por sua vez é equivalente a list.__getitem__(the_list, slice(1, None)).
Sven Marnach

4
@martineau: a cópia criada por the_list[1:]é apenas uma cópia superficial, portanto consiste em apenas um ponteiro por item da lista. A parte que consome mais memória é a zip()própria, porque criará uma lista de uma tupleinstância por item de lista, cada uma contendo dois ponteiros para os dois itens e algumas informações adicionais. Esta lista consumirá nove vezes a quantidade de memória [1:]consumida pela cópia .
Sven Marnach 25/03

19

Estou apenas divulgando isso, estou muito surpreso que ninguém tenha pensado em enumerar ().

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

11
Na verdade, a iftambém pode ser removido se você usar a divisão:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
lifebalance

2
Essa deve ser realmente a resposta, não depende de importações extras e funciona muito bem.
jamescampbell

No entanto, ele não funciona para iteráveis ​​não indexáveis, portanto não é uma solução genérica.
wim 20/04

14

A iteração por índice pode fazer a mesma coisa:

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

Resultado:

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

Sua resposta foi mais anterior e atual, em vez de atual e seguinte , como na pergunta. Fiz uma edição aprimorando a semântica para que iseja sempre o índice do elemento atual.
Bengt

1

Agora é uma importação simples A partir de 16 de maio de 2020

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

Documentos para more-itertools Sob o capô, esse código é o mesmo das outras respostas, mas prefiro importações quando disponíveis.

Se você ainda não o instalou, então: pip install more-itertools

Exemplo

Por exemplo, se você tivesse a sequência fibbonnacci, poderia calcular as proporções dos pares subsequentes como:

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

Pares de uma lista usando uma compreensão de lista

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)

Resultado:

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

0

Estou realmente surpreso que ninguém tenha mencionado a solução mais curta, mais simples e mais importante em geral :

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

Ele funciona para iteração em pares passando n=2, mas pode lidar com qualquer número maior:

>>> 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

Uma solução básica:

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)]
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.