Apa cara yang paling "pythonic" untuk beralih pada daftar dalam potongan?


488

Saya memiliki skrip Python sebagai input daftar bilangan bulat, yang saya perlukan untuk bekerja dengan empat bilangan bulat sekaligus. Sayangnya, saya tidak memiliki kendali atas input, atau saya akan meneruskannya sebagai daftar tupel empat elemen. Saat ini, saya mengulanginya dengan cara ini:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

Namun, sepertinya "C-think", yang membuat saya curiga ada cara yang lebih pythonic dalam menangani situasi ini. Daftar ini dibuang setelah iterasi, jadi tidak perlu dilestarikan. Mungkin sesuatu seperti ini akan lebih baik?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

Meskipun demikian, masih tidak terlalu "merasa" benar. : - /

Pertanyaan terkait: Bagaimana Anda membagi daftar menjadi potongan berukuran rata dengan Python?


3
Kode Anda tidak berfungsi jika ukuran daftar bukan kelipatan empat.
Pedro Henriques

5
Saya memperpanjang () daftar sehingga panjangnya adalah kelipatan dari empat sebelum sampai sejauh ini.
Ben Blank

4
@ ΤΖΩΤΖΙΟΥ - Pertanyaannya sangat mirip, tetapi tidak cukup duplikat. Ini "dipecah menjadi sejumlah bongkahan dengan ukuran N" vs. "dipecah menjadi N bongkahan dengan ukuran berapa pun". :-)
Ben Blank


Jawaban:


340

Dimodifikasi dari bagian resep dokumen itertools Python :

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Contoh
Dalam pseudocode untuk menjaga contoh tetap.

grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

Catatan: pada Python 2 gunakan izip_longestsebagai ganti zip_longest.


67
Akhirnya mendapat kesempatan untuk bermain-main dengan ini dalam sesi python. Bagi mereka yang bingung seperti saya, ini memberi makan iterator yang sama ke izip_longest beberapa kali, menyebabkannya mengkonsumsi nilai berturut-turut dari urutan yang sama daripada nilai-nilai bergaris dari urutan yang terpisah. Aku menyukainya!
Ben Blank

6
Apa cara terbaik untuk menyaring kembali nilai balik? ([item untuk item dalam item jika item tidak mengisi nilai] untuk item dalam grouper (iterable))?
Gotgenes

14
Saya menduga bahwa kinerja resep kerapu ini untuk potongan berukuran 256 ribu akan sangat buruk, karena izip_longestakan diberi argumen 256 ribu.
anatoly techtonik

13
Di beberapa tempat, komentator mengatakan "ketika saya akhirnya mengetahui bagaimana ini berhasil ...." Mungkin diperlukan sedikit penjelasan. Khususnya daftar aspek iterator.
LondonRob

6
Apakah ada cara untuk menggunakan ini tetapi tanpa Nonemengisi potongan terakhir?
CMCDragonkai

420
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

Sederhana. Mudah. Cepat. Bekerja dengan urutan apa pun:

text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']

16
Versi @Carlos Crasborn bekerja untuk semua iterable (bukan hanya urutan seperti kode di atas); itu singkat dan mungkin sama cepat atau bahkan lebih cepat. Meskipun mungkin agak tidak jelas (tidak jelas) bagi orang yang tidak terbiasa dengan itertoolsmodul.
jfs

1
Sepakat. Ini adalah cara yang paling umum dan pythonic. Jelas dan ringkas. (dan bekerja pada engine aplikasi)
Matt Williamson

3
Perhatikan bahwa chunkermengembalikan a generator. Ganti kembali ke: return [...]untuk mendapatkan daftar.
Dror

11
Alih-alih menulis sebuah bangunan fungsi dan kemudian kembali generator, Anda juga bisa menulis sebuah generator langsung, menggunakan yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Saya tidak yakin apakah secara internal ini akan ditangani secara berbeda dalam aspek yang relevan, tetapi mungkin bahkan sedikit lebih jelas.
Alfe

3
Perhatikan ini hanya berfungsi untuk urutan yang mendukung item akses oleh indeks dan tidak akan berfungsi untuk iterator generik, karena mereka mungkin tidak mendukung __getitem__metode.
apollov

135

Saya penggemar

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size

Bagaimana perilakunya jika len (ints) bukan kelipatan dari chunkSize?
PlsWork

3
@AnnaVopureta chunkakan memiliki 1, 2 atau 3 elemen untuk kumpulan elemen terakhir. Lihat pertanyaan ini tentang mengapa indeks slice bisa di luar batas .
Boris

22
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

Cara lain:

import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4

2
+1 untuk menggunakan generator, lapisan seperti yang paling "pythonic" dari semua solusi yang disarankan
Sergey Golovchenko

7
Ini agak panjang dan canggung untuk sesuatu yang sangat mudah, yang tidak terlalu pythonic sama sekali. Saya lebih suka versi S. Lott
zenazn

4
@zenazn: ini akan bekerja pada instance generator, mengiris tidak akan
Janus Troelsen

Selain bekerja dengan benar dengan generator dan iterator yang tidak dapat diiris, solusi pertama juga tidak memerlukan nilai "pengisi" jika bungkusan akhir lebih kecil dari size, yang kadang-kadang diinginkan.
dano

1
Juga memberi +1 untuk generator. Solusi lain memerlukan lenpanggilan dan karenanya tidak berfungsi pada generator lain.
Cuadue


11

Solusi ideal untuk masalah ini bekerja dengan iterator (bukan hanya urutan). Itu juga harus cepat.

Ini adalah solusi yang disediakan oleh dokumentasi untuk itertools:

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.izip_longest(fillvalue=fillvalue, *args)

Menggunakan ipython %timeit di udara buku mac saya, saya mendapatkan 47,5 kita per loop.

Namun, ini benar-benar tidak berhasil bagi saya karena hasilnya empuk untuk menjadi kelompok berukuran genap. Solusi tanpa bantalan sedikit lebih rumit. Solusi yang paling naif mungkin:

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

Sederhana, tetapi sangat lambat: 693 us per loop

Solusi terbaik yang bisa saya gunakan isliceuntuk loop dalam:

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

Dengan dataset yang sama, saya mendapatkan 305 us per loop.

Tidak dapat memperoleh solusi murni lebih cepat dari itu, saya memberikan solusi berikut dengan peringatan penting: Jika data input Anda memiliki instance filldatadi dalamnya, Anda bisa mendapatkan jawaban yang salah.

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

Saya benar-benar tidak suka jawaban ini, tetapi secara signifikan lebih cepat. 124 us per loop


Anda dapat mengurangi runtime untuk resep # 3 oleh ~ 10-15% dengan bergerak ke lapisan C (menghilangkan itertoolsimpor; mapharus Py3 mapatau imap): def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). Fungsi akhir Anda dapat dibuat kurang rapuh dengan menggunakan sentinel: singkirkan fillvalueargumen; tambahkan baris pertama fillvalue = object(), lalu ubah tanda ifcentang ke if i[-1] is fillvalue:dan baris yang dikontrolnya yield tuple(v for v in i if v is not fillvalue). Jaminan tidak ada nilai dalam iterabledapat keliru dengan nilai pengisi.
ShadowRanger

BTW, acungan jempol di # 4. Saya akan memposting optimasi saya # 3 sebagai jawaban yang lebih baik (kinerja-bijaksana) daripada apa yang telah diposting sejauh ini, tetapi dengan tweak untuk membuatnya dapat diandalkan, tangguh # 4 berjalan lebih dari dua kali lebih cepat dioptimalkan # 3; Saya tidak mengharapkan solusi dengan loop level Python (dan tidak ada perbedaan algoritme teoretis AFAICT) untuk menang. Saya berasumsi # 3 kehilangan karena biaya membangun / iterasi isliceobjek (# 3 menang jika nrelatif besar, misalnya jumlah kelompok kecil, tapi itu mengoptimalkan untuk kasus yang tidak biasa), tapi saya tidak berharap itu cukup seperti itu ekstrim.
ShadowRanger

Untuk # 4, cabang pertama dari conditional hanya diambil pada iterasi terakhir (tuple terakhir). Alih-alih membangun kembali tupel akhir lagi, cache modulo dari panjang iterable asli di bagian atas dan menggunakan itu untuk slice off padding tidak diinginkan dari izip_longestpada tuple akhir: yield i[:modulo]. Juga, untuk argsvariabel, tuple itu bukan daftar: args = (iter(iterable),) * n. Memotong beberapa siklus lagi. Terakhir, jika kita mengabaikan nilai fill dan menganggap None, kondisi bisa menjadi if None in isiklus clock bahkan lebih.
Kumba

1
@ Kumba: Saran pertama Anda mengasumsikan panjang masukan sudah diketahui. Jika iterator / generator, bukan koleksi dengan panjang yang diketahui, tidak ada yang perlu di-cache. Tidak ada alasan nyata untuk menggunakan optimasi seperti itu; Anda mengoptimalkan kasing yang tidak umum (yang terakhir yield), sedangkan kasing umum tidak terpengaruh.
ShadowRanger

10

Saya membutuhkan solusi yang juga berfungsi dengan set dan generator. Saya tidak dapat menemukan sesuatu yang sangat pendek dan cantik, tetapi setidaknya cukup mudah dibaca.

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

Daftar:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Set:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Generator:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

8

Mirip dengan proposal lain, tetapi tidak persis sama, saya suka melakukannya dengan cara ini, karena sederhana dan mudah dibaca:

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

Dengan cara ini Anda tidak akan mendapatkan potongan parsial terakhir. Jika Anda ingin mendapatkan (9, None, None, None)potongan terakhir, cukup gunakan izip_longestdari itertools.


dapat ditingkatkan denganzip(*([it]*4))
Jean-François Fabre

@ Jean-François Fabre: dari sudut pandang keterbacaan, saya tidak melihatnya sebagai peningkatan. Dan ini juga sedikit lebih lambat. Ini peningkatan jika Anda bermain golf, padahal bukan.
Kriss

tidak, saya tidak bermain golf, tetapi bagaimana jika Anda memiliki 10 argumen? Saya membaca konstruk itu di beberapa halaman resmi. Tapi tentu saja saya tidak dapat menemukannya sekarang :)
Jean-François Fabre

@ Jean-François Fabre: jika saya punya 10 argumen, atau sejumlah variabel argumen, itu pilihan, tapi saya lebih suka menulis: zip (* (itu,) * 10)
kriss

Baik! itu yang saya baca. bukan daftar hal-hal yang saya buat :)
Jean-François Fabre

8

Jika Anda tidak keberatan menggunakan paket eksternal, Anda dapat menggunakan iteration_utilities.grouperdari 1 . Ini mendukung semua iterables (bukan hanya urutan):iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

yang mencetak:

(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

Jika panjangnya bukan kelipatan dari ukuran grup, itu juga mendukung pengisian (grup terakhir yang tidak lengkap) atau pemotongan (membuang grup terakhir yang tidak lengkap) yang terakhir:

from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

Tolak ukur

Saya juga memutuskan untuk membandingkan run-time dari beberapa pendekatan yang disebutkan. Ini adalah log-log plot pengelompokan menjadi grup elemen "10" berdasarkan daftar ukuran yang berbeda-beda. Untuk hasil kualitatif: Turunkan berarti lebih cepat:

masukkan deskripsi gambar di sini

Setidaknya dalam benchmark ini iteration_utilities.grouperberkinerja terbaik. Diikuti oleh pendekatan Craz .

Benchmark dibuat dengan 1 . Kode yang digunakan untuk menjalankan tolok ukur ini adalah:simple_benchmark

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 Penafian: Saya penulis perpustakaan iteration_utilitiesdan simple_benchmark.


7

Karena belum ada yang menyebutkannya, berikut ini zip()solusinya:

>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

Ini hanya berfungsi jika panjang urutan Anda selalu dapat dibagi dengan ukuran chunk atau Anda tidak peduli dengan trailing chunk jika tidak.

Contoh:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

Atau menggunakan itertools.izip untuk mengembalikan iterator, bukan daftar:

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

Padding dapat diperbaiki menggunakan jawaban @ ::

>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)

5

Menggunakan map () alih-alih zip () memperbaiki masalah padding dalam jawaban JF Sebastian:

>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

Contoh:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

2
Ini lebih baik ditangani dengan itertools.izip_longest(Py2) / itertools.zip_longest(Py3); penggunaan ini mapsudah dua kali ditinggalkan, dan tidak tersedia di Py3 (Anda tidak bisa lulus Nonesebagai fungsi mapper, dan berhenti ketika iterable terpendek habis, bukan yang terpanjang; itu tidak pad).
ShadowRanger

4

Pendekatan lain adalah dengan menggunakan bentuk dua argumen iter:

from itertools import islice

def group(it, size):
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

Ini dapat disesuaikan dengan mudah untuk menggunakan bantalan (ini mirip dengan jawaban Markus Jarderot ):

from itertools import islice, chain, repeat

def group_pad(it, size, pad=None):
    it = chain(iter(it), repeat(pad))
    return iter(lambda: tuple(islice(it, size)), (pad,) * size)

Ini bahkan dapat dikombinasikan untuk pengisi opsional:

_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

1
lebih disukai karena Anda memiliki opsi untuk menghilangkan padding!
n611x007

3

Jika daftar besar, cara berkinerja terbaik untuk melakukannya adalah dengan menggunakan generator:

def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
    print x

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)

(Saya pikir saran itertools MizardX secara fungsional setara dengan ini.)
Robert Rossney

1
(Sebenarnya, pada refleksi, tidak saya tidak. Itertools.islice mengembalikan iterator, tetapi tidak menggunakan yang sudah ada.)
Robert Rossney

Ini bagus dan sederhana, tetapi untuk beberapa alasan bahkan tanpa konversi untuk tuple 4-7 kali lebih lambat daripada metode kerapu yang diterima pada iterable = range(100000000)& chunksizehingga 10000.
Valentas

Namun, secara umum saya akan merekomendasikan metode ini, karena yang diterima bisa sangat lambat ketika memeriksa item terakhir lambat docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

Menggunakan sedikit fungsi dan hal-hal yang sebenarnya tidak menarik bagi saya; Saya lebih suka menggunakan irisan:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...

bagus tapi tidak baik untuk aliran tanpa batas yang tidak diketahui len. Anda dapat melakukan tes dengan itertools.repeatatau itertools.cycle.
n611x007

1
Juga, makan memori karena menggunakan pemahaman [...for...] daftar untuk secara fisik membangun daftar alih-alih menggunakan (...for...) ekspresi generator yang hanya akan peduli dengan elemen berikutnya dan memori cadangan
n611x007

2

Untuk menghindari semua konversi ke daftar import itertoolsdan:

>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

Menghasilkan:

... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

Saya memeriksa groupbydan tidak dikonversi ke daftar atau digunakanlen jadi saya (berpikir) ini akan menunda resolusi dari setiap nilai sampai benar-benar digunakan. Sayangnya tidak ada jawaban yang tersedia (saat ini) yang menawarkan variasi ini.

Tentunya jika Anda perlu menangani setiap item pada gilirannya untuk loop atas g:

for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

Minat khusus saya dalam hal ini adalah kebutuhan untuk menggunakan generator untuk mengirimkan perubahan dalam batch hingga 1000 ke API gmail:

    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)

Bagaimana jika daftar yang Anda potong adalah sesuatu selain urutan bilangan bulat naik?
PaulMcG

@PaulMcGuire lihat groupby ; diberi fungsi untuk menggambarkan keteraturan maka elemen-elemen yang dapat diubah dapat berupa apa saja, bukan?
John Mee

1
Ya, saya kenal dengan groupby. Tetapi jika pesan adalah huruf "ABCDEFG", maka groupby(messages, lambda x: x/3)akan memberi Anda TypeError (untuk mencoba membagi string dengan int), bukan pengelompokan 3 huruf. Sekarang jika Anda melakukannya, groupby(enumerate(messages), lambda x: x[0]/3)Anda mungkin memiliki sesuatu. Tetapi Anda tidak mengatakan itu di posting Anda.
PaulMcG

2

Dengan NumPy itu sederhana:

ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

keluaran:

1 2
3 4
5 6
7 8

2
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()

2

Kecuali saya melewatkan sesuatu, solusi sederhana berikut dengan ekspresi generator belum disebutkan. Ini mengasumsikan bahwa baik ukuran dan jumlah bongkahan diketahui (yang sering terjadi), dan bahwa tidak ada padding diperlukan:

def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))

1

Dalam metode kedua Anda, saya akan maju ke grup 4 berikutnya dengan melakukan ini:

ints = ints[4:]

Namun, saya belum melakukan pengukuran kinerja jadi saya tidak tahu mana yang lebih efisien.

Karena itu, saya biasanya akan memilih metode pertama. Itu tidak cantik, tapi itu sering kali akibat dari berinteraksi dengan dunia luar.


1

Namun jawaban lain, kelebihannya adalah:

1) Mudah dimengerti
2) Bekerja pada setiap iterable, bukan hanya urutan (beberapa jawaban di atas akan tersedak filehandles)
3) Tidak memuat potongan ke memori sekaligus
4) Tidak membuat daftar panjang referensi chunk untuk iterator yang sama dalam memori
5) Tidak ada padding nilai mengisi pada akhir daftar

Yang sedang berkata, saya belum waktunya sehingga mungkin lebih lambat daripada beberapa metode yang lebih pintar, dan beberapa keuntungan mungkin tidak relevan mengingat use case.

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

Pembaruan:
Beberapa kelemahan karena fakta loop dalam dan luar menarik nilai dari iterator yang sama:
1) terus tidak bekerja seperti yang diharapkan di loop luar - itu hanya melanjutkan ke item berikutnya daripada melewatkan sepotong . Namun, ini sepertinya bukan masalah karena tidak ada yang bisa diuji di loop luar.
2) istirahat tidak bekerja seperti yang diharapkan dalam loop dalam - kontrol akan berakhir di loop dalam lagi dengan item berikutnya di iterator. Untuk melewati potongan utuh, bungkus bagian dalam iterator (ii di atas) dalam tuple, misalnya for c in tuple(ii), atau atur bendera dan buang knalpotnya.


1
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist

Memberi +1 menghapus padding; Anda dan bcoughlan 's sangat mirip
n611x007

1

Anda dapat menggunakan fungsi partisi atau potongan dari pustaka funcy :

from funcy import partition

for a, b, c, d in partition(4, ints):
    foo += a * b * c * d

Fungsi-fungsi ini juga memiliki versi iterator ipartitiondanichunks , yang akan lebih efisien dalam hal ini.

Anda juga dapat mengintip implementasinya .


1

Tentang solusi yang diberikan di J.F. Sebastian sini :

def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

Ini pintar, tetapi memiliki satu kelemahan - selalu mengembalikan tuple. Bagaimana cara mendapatkan string?
Tentu saja Anda dapat menulis ''.join(chunker(...)), tetapi tuple sementara tetap dibuat.

Anda dapat menyingkirkan tuple sementara dengan menulis sendiri zip, seperti ini:

class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

Kemudian

def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

Contoh penggunaan:

>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'

2
Bukan kritik yang ditujukan bagi Anda untuk mengubah jawaban Anda, melainkan sebuah komentar: Kode adalah kewajiban. Semakin banyak kode yang Anda tulis, semakin banyak ruang yang Anda buat untuk menyembunyikan bug. Dari sudut pandang ini, menulis ulang zipalih-alih menggunakan yang sudah ada tampaknya bukan ide terbaik.
Alfe

1

Saya suka pendekatan ini. Rasanya sederhana dan tidak ajaib dan mendukung semua jenis iterable dan tidak memerlukan impor.

def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk

1

Saya tidak pernah ingin potongan saya empuk, sehingga persyaratan sangat penting. Saya menemukan bahwa kemampuan untuk bekerja pada setiap iterable juga merupakan persyaratan. Karena itu, saya memutuskan untuk memperluas jawaban yang diterima, https://stackoverflow.com/a/434411/1074659 .

Kinerja mengambil sedikit hit dalam pendekatan ini jika padding tidak diinginkan karena kebutuhan untuk membandingkan dan memfilter nilai padded. Namun, untuk ukuran bongkahan besar, utilitas ini sangat performant.

#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks

1

Berikut adalah chunker tanpa impor yang mendukung generator:

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

Contoh penggunaan:

>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]

1

Dengan Python 3.8 Anda dapat menggunakan operator walrus dan itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 10)                                                         
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

0

Sepertinya tidak ada cara yang bagus untuk melakukan ini. Berikut adalah halaman yang memiliki sejumlah metode, termasuk:

def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq

0

Jika daftar memiliki ukuran yang sama, Anda dapat menggabungkannya ke dalam daftar 4-tupel zip(). Sebagai contoh:

# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

Inilah yang zip()dihasilkan fungsi:

>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

Jika daftar besar, dan Anda tidak ingin menggabungkannya ke daftar yang lebih besar, gunakan itertools.izip(), yang menghasilkan iterator, bukan daftar.

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...

0

One-liner, solusi adhoc untuk beralih ke daftar xdalam potongan ukuran 4-

for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.