Coppie da lista singola


98

Abbastanza spesso, ho riscontrato la necessità di elaborare un elenco per coppie. Mi chiedevo quale sarebbe il modo pitonico ed efficiente per farlo, e ho trovato questo su Google:

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

Ho pensato che fosse abbastanza pitonico, ma dopo una recente discussione sugli idiomi contro l'efficienza , ho deciso di fare alcuni test:

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

Questi erano i risultati sul mio computer:

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

Se li sto interpretando correttamente, ciò dovrebbe significare che l'implementazione di elenchi, indicizzazione di elenchi e affettamento di elenchi in Python è molto efficiente. È un risultato sia confortante che inaspettato.

C'è un altro modo "migliore" di attraversare una lista a coppie?

Nota che se l'elenco ha un numero dispari di elementi, l'ultimo non sarà in nessuna delle coppie.

Quale sarebbe il modo giusto per garantire che tutti gli elementi siano inclusi?

Ho aggiunto questi due suggerimenti dalle risposte ai test:

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

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

Questi sono i risultati:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

Risultati finora

Il più pitonico e molto efficiente:

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

Il più efficiente e molto pitonico:

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

Mi ci è voluto un momento per capire che la prima risposta utilizza due iteratori mentre la seconda ne utilizza uno solo.

Per trattare sequenze con un numero dispari di elementi, il suggerimento è stato quello di aumentare la sequenza originale aggiungendo un elemento ( None) che viene accoppiato con l'ultimo elemento precedente, qualcosa che può essere ottenuto con itertools.izip_longest().

Finalmente

Nota che, in Python 3.x, zip()si comporta come itertools.izip(), e non itertools.izip() c'è più.


RE: il "modo giusto" - non esiste un modo "giusto"! Dipende dal caso d'uso.
Andrew Jaffe

@Andrew Jaffe ho dato i criteri per "migliore" in questo caso: efficiente e pitonico.
Apalala

@Apalala: voglio dire che il risultato di avere un numero dispari dipende dall'uso. Ad esempio: potresti semplicemente tralasciare l'ultimo elemento, o aggiungere uno specifico elemento fittizio noto, o duplicare l'ultimo
Andrew Jaffe

2
@Apalala: perché stai usando un mumbo-jumbo al posto del timeitmodulo.
SilentGhost

Risposte:


52

Il mio modo preferito per farlo:

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)

Quando vuoi accoppiare tutti gli elementi potresti ovviamente aver bisogno di un valore di riempimento:

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

La prima funzione (a coppie) sembra mancare della clonazione e dell'avanzamento del secondo iteratore. Vedi la itertoolssezione ricette.
Apalala

@Apalala: zip fa avanzare lo stesso iteratore due volte.
Jochen Ritzel

Certo, hai ragione e il pairwise è il più efficiente finora, non so perché.
Apalala

1
Adoro questa soluzione: è pigra e sfrutta lo statefulness degli iteratori con grande effetto. Potresti persino renderlo un one-liner, anche se forse a scapito della leggibilità:izip(*[iter(t)]*size)
Channing Moore

per la tua seconda soluzione, non vorresti evitare di creare una lista se andassi dopo la performance?
max

40

Direi che la tua soluzione iniziale pairs = zip(t[::2], t[1::2])è la migliore perché è più facile da leggere (e in Python 3, ziprestituisce automaticamente un iteratore invece di un elenco).

Per assicurarti che tutti gli elementi siano inclusi, potresti semplicemente estendere l'elenco di None.

Quindi, se l'elenco ha un numero dispari di elementi, l'ultima coppia sarà (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)]

6

Comincio con una piccola dichiarazione di non responsabilità: non utilizzare il codice seguente. Non è affatto Pythonic, ho scritto solo per divertimento. È simile alla pairwisefunzione @ THC4k ma usa itere chiude lambda. Non usa il itertoolsmodulo e non supporta fillvalue. L'ho messo qui perché qualcuno potrebbe trovarlo interessante:

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

3

Per quanto riguarda la maggior parte dei pythonic, direi che le ricette fornite nei documenti sorgente di Python (alcune delle quali assomigliano molto alle risposte fornite da @JochenRitzel) sono probabilmente la soluzione migliore;)

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)

2

C'è un altro modo "migliore" di attraversare una lista a coppie?

Non posso dirlo con certezza ma ne dubito: qualsiasi altro attraversamento includerebbe più codice Python che deve essere interpretato. Le funzioni integrate come zip () sono scritte in C, che è molto più veloce.

Quale sarebbe il modo giusto per garantire che tutti gli elementi siano inclusi?

Controlla la lunghezza dell'elenco e, se è dispari ( len(list) & 1 == 1), copia l'elenco e aggiungi un elemento.


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

IndexError: pop da un elenco vuoto
HQuser

@HQuser Certo, riceverai quell'errore se hai un numero dispari di elementi nell'elenco. Devi sapere per certo di avere delle coppie o controllare questa condizione di errore.
WaterMolecule

0

Fallo solo:

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

Il tuo codice è equivalente al più semplice list(zip(l, l[1:]))e non divide l'elenco in coppie.
Apalala

0

Ecco un esempio di creazione di coppie / gambe utilizzando un generatore. I generatori non hanno limiti di stack

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

Esempio:

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

Produzione:

[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

Confronto del tempo di esecuzione?
Alan

L'elenco non è suddiviso in coppie, poiché la maggior parte dei numeri nell'elenco originale appare in due tuple. L'uscita prevista è[(0, 1), (2, 3), (4, 5)....
Apalala il

@Apalala grazie per averlo sottolineato. Ho corretto il codice per fornire l'output corretto
Vlad Bezden

zip()restituisce già un generatore in Python 3.x, @VladBezden
Apalala il

-1

Nel caso in cui qualcuno abbia bisogno della risposta dal punto di vista dell'algoritmo, eccola:

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

Ma tieni presente che anche il tuo elenco originale sarà ridotto al suo ultimo elemento, perché lo hai utilizzato pop.

>>> k
[4]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.