web-dev-qa-db-fra.com

Paires d'une seule liste

Souvent, j'ai trouvé la nécessité de traiter une liste par paires. Je me demandais quelle serait la façon Pythonique et efficace de le faire, et j'ai trouvé ceci sur Google:

pairs = Zip(t[::2], t[1::2])

Je pensais que c'était assez Pythonic, mais après une récente discussion impliquant idiomes versus efficacité , j'ai décidé de faire quelques tests:

import time
from itertools import islice, izip

def pairs_1(t):
    return Zip(t[::2], t[1::2]) 

def pairs_2(t):
    return izip(t[::2], t[1::2]) 

def pairs_3(t):
    return izip(islice(t,None,None,2), islice(t,1,None,2))

A = range(10000)
B = xrange(len(A))

def pairs_4(t):
    # ignore value of t!
    t = B
    return izip(islice(t,None,None,2), islice(t,1,None,2))

for f in pairs_1, pairs_2, pairs_3, pairs_4:
    # time the pairing
    s = time.time()
    for i in range(1000):
        p = f(A)
    t1 = time.time() - s

    # time using the pairs
    s = time.time()
    for i in range(1000):
        p = f(A)
        for a, b in p:
            pass
    t2 = time.time() - s
    print t1, t2, t2-t1

Ce sont les résultats sur mon ordinateur:

1.48668909073 2.63187503815 1.14518594742
0.105381965637 1.35109519958 1.24571323395
0.00257992744446 1.46182489395 1.45924496651
0.00251388549805 1.70076990128 1.69825601578

Si je les interprète correctement, cela devrait signifier que l'implémentation de listes, l'indexation de listes et le découpage de listes en Python est très efficace. C'est un résultat à la fois réconfortant et inattendu.

Existe-t-il une autre, "meilleure" manière de parcourir une liste par paires?

Notez que si la liste a un nombre impair d'éléments, le dernier ne sera dans aucune des paires.

Quelle serait la bonne façon de s'assurer que tous les éléments sont inclus?

J'ai ajouté ces deux suggestions à partir des réponses aux tests:

def pairwise(t):
    it = iter(t)
    return izip(it, it)

def chunkwise(t, size=2):
    it = iter(t)
    return izip(*[it]*size)

Voici les résultats:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

Résultats à ce jour

Le plus pythonique et le plus efficace:

pairs = izip(t[::2], t[1::2])

Le plus efficace et le plus Pythonic:

pairs = izip(*[iter(t)]*2)

Il m'a fallu un moment pour comprendre que la première réponse utilise deux itérateurs tandis que la seconde en utilise un seul.

Pour traiter les séquences avec un nombre impair d'éléments, la suggestion a été d'augmenter la séquence originale en ajoutant un élément (None) qui est couplé avec le dernier élément précédent, quelque chose qui peut être réalisé avec itertools.izip_longest().

Finalement

Notez que, dans Python 3.x, Zip() se comporte comme itertools.izip() et itertools.izip() a disparu.

83
Apalala

Ma façon préférée de le faire:

from itertools import izip

def pairwise(t):
    it = iter(t)
    return izip(it,it)

# for "pairs" of any length
def chunkwise(t, size=2):
    it = iter(t)
    return izip(*[it]*size)

Lorsque vous souhaitez associer tous les éléments, vous pouvez évidemment avoir besoin d'une valeur de remplissage:

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)
41
Jochen Ritzel

Je dirais que votre solution initiale pairs = Zip(t[::2], t[1::2]) est la meilleure car elle est plus facile à lire (et en Python 3, Zip retourne automatiquement un itérateur) au lieu d'une liste).

Pour vous assurer que tous les éléments sont inclus, vous pouvez simplement étendre la liste de None.

Ensuite, si la liste a un nombre impair d'éléments, la dernière paire sera (item, None).

>>> t = [1,2,3,4,5]
>>> t.append(None)
>>> Zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, None)]
>>> t = [1,2,3,4,5,6]
>>> t.append(None)
>>> Zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, 6)]
36
Tim Pietzcker

Je commence par un petit avertissement - n'utilisez pas le code ci-dessous. Ce n'est pas du tout Pythonic, j'ai écrit juste pour le plaisir. Elle est similaire à la fonction @ THC4k pairwise mais elle utilise les fermetures iter et lambda. Il n'utilise pas le module itertools et ne prend pas en charge fillvalue. Je l'ai mis ici parce que quelqu'un pourrait le trouver intéressant:

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)
6
Tomasz Elendt

En ce qui concerne la plupart des Pythonic, je dirais que les recettes fournies dans les python sources docs (dont certaines ressemblent beaucoup aux réponses fournies par @JochenRitzel) est probablement votre meilleur pari;)

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)
3
Pat

Existe-t-il une autre, "meilleure" manière de parcourir une liste par paires?

Je ne peux pas le dire avec certitude, mais j'en doute: toute autre traversée inclurait plus de code Python qui doit être interprété. Les fonctions intégrées comme Zip () sont écrites en C qui est Plus vite.

Quelle serait la bonne façon de s'assurer que tous les éléments sont inclus?

Vérifiez la longueur de la liste et si elle est impaire (len(list) & 1 == 1), copiez la liste et ajoutez un élément.

2
Aaron Digulla
>>> my_list = [1,2,3,4,5,6,7,8,9,10]
>>> my_pairs = list()
>>> while(my_list):
...     a = my_list.pop(0); b = my_list.pop(0)
...     my_pairs.append((a,b))
... 
>>> print(my_pairs)
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
0
Diarmuid O'Briain