Pasangan dari daftar tunggal

98

Cukup sering, saya merasa perlu memproses daftar secara berpasangan. Saya bertanya-tanya mana yang akan menjadi cara pythonic dan efisien untuk melakukannya, dan menemukan ini di Google:

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

Saya pikir itu cukup pythonic, tetapi setelah diskusi baru-baru ini yang melibatkan idiom versus efisiensi , saya memutuskan untuk melakukan beberapa tes:

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

Ini adalah hasil di komputer saya:

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

Jika saya menafsirkannya dengan benar, itu berarti penerapan daftar, pengindeksan daftar, dan pemotongan daftar dengan Python sangat efisien. Hasilnya menghibur dan tidak terduga.

Apakah ada cara lain yang "lebih baik" untuk melintasi daftar secara berpasangan?

Perhatikan bahwa jika daftar memiliki jumlah elemen ganjil maka yang terakhir tidak akan ada di pasangan mana pun.

Cara manakah yang benar untuk memastikan bahwa semua elemen disertakan?

Saya menambahkan dua saran ini dari jawaban tes:

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

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

Inilah hasilnya:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

Hasil sejauh ini

Paling pythonic dan sangat efisien:

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

Paling efisien dan sangat pythonic:

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

Butuh beberapa saat bagi saya untuk menjelaskan bahwa jawaban pertama menggunakan dua iterator sedangkan yang kedua menggunakan satu satu.

Untuk menangani urutan dengan jumlah elemen ganjil, sarannya adalah menambah urutan asli dengan menambahkan satu elemen ( None) yang dipasangkan dengan elemen terakhir sebelumnya, sesuatu yang dapat dicapai dengan itertools.izip_longest().

Akhirnya

Perhatikan bahwa, dalam Python 3.x, zip()berperilaku sebagai itertools.izip(), dan itertools.izip() hilang.

Apalala
sumber
RE: cara yang "benar" - tidak ada cara yang "benar"! Itu tergantung pada kasus penggunaan.
Andrew Jaffe
@Andrew Jaffe Saya memberikan kriteria "terbaik" dalam hal ini: efisien, dan pythonic.
Apalala
@ Apalala: Maksud saya, hasil dari memiliki bilangan ganjil tergantung pada penggunaannya. Misalnya: Anda bisa meninggalkan elemen terakhir, atau menambahkan elemen dummy tertentu yang diketahui, atau menduplikasi yang terakhir
Andrew Jaffe
2
@ Apalala: karena Anda menggunakan beberapa omong kosong sebagai ganti timeitmodul.
SilentGhost

Jawaban:

52

Cara favorit saya untuk melakukannya:

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)

Saat Anda ingin memasangkan semua elemen, Anda jelas membutuhkan fillvalue:

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)
Jochen Ritzel
sumber
Fungsi pertama (berpasangan) tampaknya kehilangan kloning dan kemajuan dari iterator kedua. Lihat bagian itertoolsresep.
Apalala
@Apalala: zip memajukan iterator yang sama dua kali.
Jochen Ritzel
Tentu saja, Anda benar, dan berpasangan adalah yang paling efisien sejauh ini, saya tidak tahu mengapa.
Apalala
1
Saya suka solusi ini: solusi ini malas, dan ini mengeksploitasi statefulness iterator dengan sangat efektif. Anda bahkan dapat membuatnya menjadi satu baris, meskipun mungkin dengan mengorbankan keterbacaan:izip(*[iter(t)]*size)
Channing Moore
untuk solusi kedua Anda, tidakkah Anda ingin menghindari membuat daftar jika mengejar kinerja?
maks
41

Saya akan mengatakan bahwa solusi awal Anda pairs = zip(t[::2], t[1::2])adalah yang terbaik karena paling mudah dibaca (dan di Python 3, zipsecara otomatis mengembalikan iterator daripada daftar).

Untuk memastikan bahwa semua elemen disertakan, Anda cukup memperpanjang daftar dengan None.

Kemudian, jika daftarnya memiliki jumlah elemen ganjil, pasangan terakhirnya adalah (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
sumber
6

Saya mulai dengan penafian kecil - jangan gunakan kode di bawah ini. Itu sama sekali bukan Pythonic, saya menulis hanya untuk bersenang-senang. Ini mirip dengan pairwisefungsi @ THC4k tetapi menggunakan iterdan menutup lambda. Itu tidak menggunakan itertoolsmodul dan tidak mendukung fillvalue. Saya taruh di sini karena seseorang mungkin menganggapnya menarik:

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

Sejauh kebanyakan pythonic pergi, saya akan mengatakan resep yang disediakan dalam dokumen sumber python (beberapa di antaranya terlihat sangat mirip dengan jawaban yang disediakan @JochenRitzel) mungkin adalah pilihan terbaik Anda;)

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)

Pada python modern Anda hanya perlu menggunakan zip_longest(*args, fillvalue=fillvalue) sesuai dengan halaman dokumen yang sesuai .

Menepuk
sumber
2

Apakah ada cara lain yang "lebih baik" untuk melintasi daftar secara berpasangan?

Saya tidak dapat mengatakan dengan pasti tetapi saya meragukannya: Setiap traversal lain akan menyertakan lebih banyak kode Python yang harus ditafsirkan. Fungsi bawaan seperti zip () ditulis dalam C yang jauh lebih cepat.

Cara manakah yang benar untuk memastikan bahwa semua elemen disertakan?

Periksa panjang daftar dan jika ganjil ( len(list) & 1 == 1), salin daftar dan tambahkan item.

Aaron Digulla
sumber
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
sumber
IndexError: muncul dari daftar kosong
HQuser
@HQuser Tentu, Anda akan mendapatkan kesalahan itu jika Anda memiliki jumlah item ganjil dalam daftar. Anda harus tahu pasti bahwa Anda memiliki pasangan atau memeriksa kondisi kesalahan ini.
WaterMolecule
0

Lakukan saja:

>>> 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)]
Israel Gonçaves de Oliveira
sumber
Kode Anda setara dengan yang lebih sederhana list(zip(l, l[1:])), dan tidak membagi daftar menjadi pasangan.
Apalala
0

Berikut contoh pembuatan pasangan / kaki dengan menggunakan generator. Generator bebas dari batas tumpukan

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

Contoh:

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

Keluaran:

[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Vlad Bezden
sumber
Perbandingan waktu eksekusi?
Alan
Daftar ini tidak dipecah menjadi pasangan, karena kebanyakan angka dalam daftar asli muncul dalam dua tupel. Output yang diharapkan adalah[(0, 1), (2, 3), (4, 5)....
Apalala
@Apalala terima kasih telah menunjukkan. Saya memperbaiki kode untuk memberikan hasil yang benar
Vlad Bezden
zip()sudah mengembalikan generator dengan Python 3.x, @VladBezden
Apalala
-1

Untuk berjaga-jaga jika seseorang membutuhkan jawaban dari segi algoritme, ini dia:

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

Tetapi perhatikan bahwa daftar asli Anda juga akan dikurangi menjadi elemen terakhirnya, karena Anda menggunakannya pop.

>>> k
[4]
frainmaster
sumber