Paires d'une seule liste

98

Assez souvent, j'ai trouvé la nécessité de traiter une liste par paires. Je me demandais quel serait le moyen 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 pythonique, mais après une discussion récente entre les idiomes et l'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

Voici 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 la mise en œuvre de listes, l'indexation de listes et le découpage de listes en Python sont très efficaces. C'est un résultat à la fois réconfortant et inattendu.

Existe-t-il une autre «meilleure» façon 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 pythonique:

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 des séquences avec un nombre impair d'éléments, la suggestion a été d'augmenter la séquence d'origine en ajoutant un élément ( None) qui est associé au dernier élément précédent, ce 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() est parti.

Apalala
la source
RE: la "bonne manière" - il n'y a pas de "bonne" voie! Cela dépend du cas d'utilisation.
Andrew Jaffe
@Andrew Jaffe J'ai donné les critères du "meilleur" dans ce cas: efficace et pythonique.
Apalala
@Apalala: Je veux dire que le résultat d'avoir un nombre impair dépend de l'utilisation. Par exemple: vous pouvez simplement supprimer le dernier élément, ou ajouter un élément factice connu spécifique, ou dupliquer le dernier
Andrew Jaffe
2
@Apalala: parce que vous utilisez du mumbo-jumbo au lieu du timeitmodule.
SilentGhost

Réponses:

52

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)
Jochen Ritzel
la source
La première fonction (par paires) semble manquer le clonage et l'avancement du deuxième itérateur. Voir la itertoolssection recettes.
Apalala
@Apalala: zip avance deux fois le même itérateur.
Jochen Ritzel
Bien sûr, vous avez raison, et la paire est la plus efficace à ce jour, je ne sais pas pourquoi.
Apalala
1
J'adore cette solution: elle est paresseuse, et elle exploite l'état des itérateurs à bon escient. Vous pourriez même en faire un one-liner, mais peut-être au détriment de la lisibilité:izip(*[iter(t)]*size)
Channing Moore
pour votre deuxième solution, ne voudriez-vous pas éviter de créer une liste si vous recherchez la performance?
max
41

Je dirais que votre solution initiale pairs = zip(t[::2], t[1::2])est la meilleure car elle est la plus facile à lire (et en Python 3, ziprenvoie 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 le 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)]
Tim Pietzcker
la source
6

Je commence par une petite clause de non-responsabilité - n'utilisez pas le code ci-dessous. Ce n'est pas du tout Pythonic, j'ai écrit juste pour m'amuser. C'est similaire à la pairwisefonction @ THC4k mais elle utilise iteret se lambdaferme. Il n'utilise pas de itertoolsmodule 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)
Tomasz Elendt
la source
4

En ce qui concerne la plupart des pythons, je dirais que les recettes fournies dans la documentation des sources python (dont certaines ressemblent beaucoup aux réponses fournies par @JochenRitzel) sont 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)

Sur python moderne, il vous suffit d'utiliser zip_longest(*args, fillvalue=fillvalue) selon la page doc correspondante .

Tapoter
la source
2

Existe-t-il une autre «meilleure» façon 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, ce qui est beaucoup plus rapide.

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 c'est impair ( len(list) & 1 == 1), copiez la liste et ajoutez un élément.

Aaron Digulla
la source
2
>>> 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)]
Diarmuid O'Briain
la source
IndexError: pop from empty list
HQuser
@HQuser Bien sûr, vous obtiendrez cette erreur si vous avez un nombre impair d'éléments dans la liste. Vous devez être sûr que vous avez des paires ou vérifier cette condition d'erreur.
WaterMolecule
0

Faites-le seulement:

>>> l = [1, 2, 3, 4, 5, 6]
>>> [(x,y) for x,y in zip(l[:-1], l[1:])]
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
Israël Gonçaves de Oliveira
la source
Votre code est équivalent au plus simple list(zip(l, l[1:])), et il ne divise pas la liste en paires.
Apalala le
0

Voici un exemple de création de paires / jambes à l'aide d'un générateur. Les générateurs sont exempts de limites de pile

def pairwise(data):
    zip(data[::2], data[1::2])

Exemple:

print(list(pairwise(range(10))))

Production:

[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Vlad Bezden
la source
Comparaison du temps d'exécution?
Alan
La liste n'est pas divisée en paires, car la plupart des nombres de la liste d'origine apparaissent en deux tuples. La sortie attendue est[(0, 1), (2, 3), (4, 5)....
Apalala le
@Apalala merci de l'avoir signalé. J'ai corrigé le code pour fournir la bonne sortie
Vlad Bezden
zip()renvoie déjà un générateur en Python 3.x, @VladBezden
Apalala le
-1

Juste au cas où quelqu'un aurait besoin de la réponse en termes d'algorithme, la voici:

>>> def getPairs(list):
...     out = []
...     for i in range(len(list)-1):
...         a = list.pop(0)
...         for j in a:
...             out.append([a, j])
...     return b
>>> 
>>> k = [1, 2, 3, 4]
>>> l = getPairs(k)
>>> l
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

Mais notez que votre liste d'origine sera également réduite à son dernier élément, car vous l'avez utilisée pop.

>>> k
[4]
frainmaster
la source