Itérer une liste sous forme de paire (actuelle, suivante) en Python

130

J'ai parfois besoin d'itérer une liste en Python en regardant l'élément "courant" et l'élément "suivant". Jusqu'à présent, je l'ai fait avec du code comme:

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

Cela fonctionne et fait ce que j'attends, mais y a-t-il un moyen plus idiomatique ou plus efficace de faire la même chose?

dcrosta
la source
Vérifiez la réponse de MizardX pour cette question . Mais je ne pense pas que cette solution soit plus idiomatique que la vôtre.
Fábio Diniz
2
Jetez un œil à Créer un itérateur Python de base .
mkluwe
39
puisque personne d'autre ne l'a mentionné, je serai ce gars-là, et je ferai remarquer que l'utilisation de nextcette façon masque un fichier intégré.
senderle
@senderle Peut-être que c'est Python 2…
Quintec
2
@ thecoder16: nextest également une fonction intégrée dans Python 2.
zondo

Réponses:

131

Voici un exemple pertinent tiré de la documentation du module 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)   

Pour Python 2, vous avez besoin itertools.izipau lieu 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)

Comment ça marche:

Tout d' abord, deux itérateurs parallèles, aet bsont créés (l' tee()appel), les deux pointant vers le premier élément de la iterable originale. Le deuxième itérateur, best déplacé d'un pas en avant (l' next(b, None)appel). À ce stade, apointe vers s0 et bpointe vers s1. Les deux aet bpeuvent parcourir l'itérateur d'origine indépendamment - la fonction izip prend les deux itérateurs et crée des paires d'éléments retournés, en faisant avancer les deux itérateurs au même rythme.

Une mise en garde: la tee()fonction produit deux itérateurs qui peuvent avancer indépendamment l'un de l'autre, mais cela a un coût. Si l'un des itérateurs avance plus loin que l'autre, alorstee() doit conserver les éléments consommés en mémoire jusqu'à ce que le deuxième itérateur les reprenne également (il ne peut pas «rembobiner» l'itérateur d'origine). Ici, cela n'a pas d'importance car un itérateur n'a qu'une longueur d'avance sur l'autre, mais en général, il est facile d'utiliser beaucoup de mémoire de cette façon.

Et comme tee()peut prendre un nparamètre, cela peut également être utilisé pour plus de deux itérateurs parallèles:

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)
Rafał Dowgird
la source
4
L'exemple de code est excellent ... mais pouvez-vous expliquer un peu pourquoi cela fonctionne? Comme dire ce que font ici "tee ()" et "next ()".
John Mulder
@John Mulder: A fait un bref résumé.
Rafał Dowgird
9
zip(ł, ł[1:])est beaucoup plus court et pythonique
noɥʇʎԀʎzɐɹƆ
2
@ noɥʇʎԀʎzɐɹƆ: Non, cela ne fonctionne pas sur tous les itérables et fait une copie inutile lorsqu'il est utilisé sur des listes. L'utilisation des fonctions est pythonique.
Ry-
Cette fonction implémentée dans le funcymodule: funcy.pairwise: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR
30

Roulez le vôtre!

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

    for b in it:
        yield (a, b)
        a = b
Ry-
la source
1
Juste ce dont j'avais besoin! Cela a-t-il été immortalisé en tant que méthode python, ou devons-nous continuer à rouler?
uhoh
1
@uhoh: Pas encore pour autant que je sache!
Ry-
21

Puisque the_list[1:]crée en fait une copie de la liste entière (à l'exclusion de son premier élément), et zip()crée une liste de tuples immédiatement lorsqu'elle est appelée, au total trois copies de votre liste sont créées. Si votre liste est très longue, vous préférerez peut-être

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

qui ne copie pas du tout la liste.

Sven Marnach
la source
3
notez qu'en python 3.x izip est supprimé d'itertools et que vous devez utiliser le zip intégré
Xavier Combelle
1
En fait, ne crée pas the_list[1:]seulement un objet de tranche plutôt qu'une copie de presque toute la liste - la technique de l'OP n'est donc pas aussi inutile que vous la faites paraître.
martineau
3
Je pense que [1:]crée l'objet slice (ou éventuellement " 1:"), qui est passé __slice__dans la liste, qui retourne ensuite une copie contenant uniquement les éléments sélectionnés. Une façon idiomatique de copier une liste est l_copy = l[:](que je trouve moche et illisible - je préfère l_copy = list(l))
dcrosta
4
@dcrosta: Il n'y a pas de __slice__méthode spéciale. the_list[1:]est équivalent à the_list[slice(1, None)], qui à son tour équivaut à list.__getitem__(the_list, slice(1, None)).
Sven Marnach
4
@martineau: La copie créée par the_list[1:]n'est qu'une copie superficielle, elle se compose donc d'un seul pointeur par élément de liste. La partie la plus gourmande en mémoire est zip()elle - même, car elle créera une liste d'une tupleinstance par élément de liste, chacune contenant deux pointeurs vers les deux éléments et des informations supplémentaires. Cette liste consommera neuf fois la quantité de mémoire consommée par la copie [1:].
Sven Marnach
19

Je suis juste en train de sortir ça, je suis très surpris que personne n'ait pensé à enumerate ().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Quintec
la source
11
En fait, le ifpeut également être supprimé si vous utilisez le tranchage:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
lifebalance
2
Cela devrait vraiment être la réponse, cela ne repose sur aucune importation supplémentaire et fonctionne très bien.
jamescampbell
Cependant, cela ne fonctionne pas pour les itérables non indexables, ce n'est donc pas une solution générique.
wim
14

Itérer par index peut faire la même chose:

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

Production:

(1, 2)
(2, 3)
(3, 4)
Peau d'échasses Rumple
la source
Votre réponse était plus précédente et actuelle plutôt que actuelle et suivante , comme dans la question. J'ai fait une modification améliorant la sémantique pour que ce isoit toujours l'index de l'élément courant.
Bengt le
1

Ceci est maintenant une simple importation à partir du 16 mai 2020

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

Docs pour plus-itertools Sous le capot, ce code est le même que celui des autres réponses, mais je préfère de les importations lorsqu'elles sont disponibles.

Si vous ne l'avez pas déjà installé, alors: pip install more-itertools

Exemple

Par exemple, si vous aviez la séquence fibbonnacci, vous pourriez calculer les ratios des paires suivantes comme suit:

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} ')
jabberwocky
la source
0

Paires d'une liste en utilisant une compréhension de liste

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)

Production:

(1, 2)
(2, 3)
(3, 4)
Bengt
la source
0

Je suis vraiment surpris que personne n'ait mentionné la solution la plus courte, la plus simple et surtout générale :

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

Cela fonctionne pour une itération par paire en passant n=2, mais peut gérer tout nombre supérieur:

>>> 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 !
Marco Bonelli
la source
-2

Une solution basique:

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 )
mkluwe
la source
-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)]
Russell Wong
la source