Bagaimana Anda membagi daftar menjadi potongan yang berukuran rata?

2269

Saya memiliki daftar panjang yang sewenang-wenang, dan saya perlu membaginya menjadi potongan-potongan ukuran yang sama dan beroperasi di atasnya. Ada beberapa cara yang jelas untuk melakukan ini, seperti menyimpan penghitung dan dua daftar, dan ketika daftar kedua terisi, tambahkan ke daftar pertama dan kosongkan daftar kedua untuk putaran data berikutnya, tetapi ini berpotensi sangat mahal.

Saya bertanya-tanya apakah ada yang punya solusi yang baik untuk ini untuk daftar berapa pun, misalnya menggunakan generator.

Saya mencari sesuatu yang bermanfaat itertoolstetapi saya tidak dapat menemukan sesuatu yang jelas berguna. Namun, mungkin saja Anda melewatkannya.

Pertanyaan terkait: Apa cara paling "pythonic" untuk beralih pada daftar dalam potongan?

yesus
sumber
1
Sebelum Anda memposting jawaban baru, pertimbangkan sudah ada 60+ jawaban untuk pertanyaan ini. Harap pastikan bahwa jawaban Anda menyumbangkan informasi yang tidak ada di antara jawaban yang ada.
janniks
Untuk pengguna yang ingin menghindari potongan akhir yang kecil dan sewenang-wenang, lihat Memecah daftar menjadi N bagian dengan panjang kira-kira sama
wim

Jawaban:

3152

Inilah generator yang menghasilkan bongkahan yang Anda inginkan:

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

import pprint
pprint.pprint(list(chunks(range(10, 75), 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]]

Jika Anda menggunakan Python 2, Anda harus menggunakan xrange()alih-alih range():

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in xrange(0, len(lst), n):
        yield lst[i:i + n]

Anda juga dapat menggunakan pemahaman daftar alih-alih menulis fungsi, meskipun merupakan ide bagus untuk merangkum operasi seperti ini dalam fungsi yang dinamai sehingga kode Anda lebih mudah dipahami. Python 3:

[lst[i:i + n] for i in range(0, len(lst), n)]

Versi Python 2:

[lst[i:i + n] for i in xrange(0, len(lst), n)]
Ned Batchelder
sumber
72
Apa yang terjadi jika kita tidak dapat mengetahui panjang daftar? Coba ini di itertools.repeat ([1, 2, 3]), mis.
jespern
47
Itu ekstensi yang menarik untuk pertanyaan, tetapi pertanyaan asli dengan jelas bertanya tentang pengoperasian daftar.
Ned Batchelder
33
fungsi ini harus ada di perpustakaan standar sialan
dgan
6
@ Calimo: apa yang Anda sarankan? Saya memberi Anda daftar dengan 47 elemen. Bagaimana Anda ingin membaginya menjadi "potongan berukuran rata"? OP menerima jawabannya, jadi mereka jelas baik-baik saja dengan potongan ukuran terakhir yang berbeda. Mungkin frasa bahasa Inggrisnya tidak tepat?
Ned Batchelder
8
Tolong jangan beri nama variabel Anda l, sepertinya persis 1 dan membingungkan. Orang-orang menyalin kode Anda dan berpikir ini tidak masalah.
Yasen
555

Jika Anda menginginkan sesuatu yang super sederhana:

def chunks(l, n):
    n = max(1, n)
    return (l[i:i+n] for i in range(0, len(l), n))

Gunakan xrange()sebagai ganti range()dalam kasus Python 2.x

oremj
sumber
6
Atau (jika kita melakukan representasi berbeda dari fungsi khusus ini) Anda dapat mendefinisikan fungsi lambda melalui: lambda x, y: [x [i: i + y] untuk i dalam range (0, len (x), y) ] Saya suka metode pemahaman daftar ini!
JP
4
setelah kembali harus ada [, bukan (
alwbtc
2
"Super simpel" berarti tidak harus men - debug loop tak terbatas - pujian untuk max().
Bob Stein
tidak ada yang sederhana tentang solusi ini
mit
1
@Nhoj_Gonk Ups itu bukan loop tak terbatas, tetapi potongan (L, 0) akan menaikkan ValueError tanpa maks (). Sebaliknya, maks () mengubah sesuatu yang kurang dari 1 menjadi 1.
Bob Stein
295

Langsung dari dokumentasi Python (lama) (resep untuk itertools):

from itertools import izip, chain, repeat

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

Versi saat ini, seperti yang disarankan oleh JFSebastian:

#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

Saya kira mesin waktu Guido bekerja — bekerja — akan bekerja — akan berhasil — bekerja lagi.

Solusi ini berfungsi karena [iter(iterable)]*n(atau yang setara di versi sebelumnya) membuat satu iterator, berulang nkali dalam daftar. izip_longestkemudian secara efektif melakukan round-robin dari iterator "masing-masing"; karena ini adalah iterator yang sama, ia dikuatkan oleh setiap panggilan tersebut, menghasilkan setiap zip-roundrobin menghasilkan satu tuple nitem.

tzot
sumber
@ninjagecko: list(grouper(3, range(10)))pengembalian [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)], dan semua tuple memiliki panjang 3. Tolong jelaskan komentar Anda karena saya tidak bisa memahaminya; apa yang Anda sebut suatu hal dan bagaimana Anda mendefinisikannya sebagai kelipatan 3 dalam “mengharapkan hal Anda menjadi kelipatan 3”? Terima kasih sebelumnya.
tzot
14
memutakhirkan ini karena ia bekerja pada generator (no len) dan menggunakan modul itertools yang umumnya lebih cepat.
Michael Dillon
88
Sebuah contoh klasik dari itertoolspendekatan fungsional mewah menghasilkan beberapa lumpur yang tidak dapat dibaca, bila dibandingkan dengan implementasi python murni yang sederhana dan naif
wim
15
@wim Mengingat bahwa jawaban ini dimulai sebagai cuplikan dari dokumentasi Python, saya sarankan Anda membuka masalah di bugs.python.org .
tzot
1
@pedrosaurio jika l==[1, 2, 3]itu f(*l)setara dengan f(1, 2, 3). Lihat pertanyaan itu dan dokumentasi resmi .
tzot
227

Saya tahu ini agak tua tetapi belum ada yang menyebutkan numpy.array_split:

import numpy as np

lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
#  array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
#  array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
#  array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
#  array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]
Moj
sumber
12
Ini memungkinkan Anda untuk mengatur jumlah total potongan, bukan jumlah elemen per potongan.
FizxMike
6
Anda bisa menghitung sendiri. jika Anda memiliki 10 elemen, Anda dapat mengelompokkannya menjadi 2, 5 elemen, atau lima 2 elemen,
Moj
24
+1 Ini adalah solusi favorit saya, karena memecah array menjadi array berukuran merata , sedangkan solusi lain tidak (dalam semua solusi lain yang saya lihat, array terakhir mungkin kecil sembarang).
MiniQuark
@MiniQuark tetapi apa fungsinya ketika jumlah blok bukan merupakan faktor dari ukuran array asli?
Baldrickk
1
@Baldrickk Jika Anda membagi elemen N menjadi potongan K, maka potongan N% K pertama akan memiliki elemen N // K + 1, dan sisanya akan memiliki elemen N // K. Misalnya, jika Anda membagi array yang berisi 108 elemen menjadi 5 chunks, maka 108% 5 pertama = 3 chunks akan berisi 108 // 5 + 1 = 22 elemen, dan sisa chunks akan memiliki 108 // 5 = 21 elemen.
MiniQuark
147

Aku heran tidak ada yang berpikir untuk menggunakan iter's bentuk dua-argumen :

from itertools import islice

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

Demo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]

Ini bekerja dengan iterable dan menghasilkan output dengan malas. Ini mengembalikan tuple daripada iterator, tapi saya pikir itu memiliki keanggunan tertentu. Itu juga tidak pad; jika Anda ingin bantalan, variasi sederhana di atas sudah cukup:

from itertools import islice, chain, repeat

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

Demo:

>>> list(chunk_pad(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk_pad(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Seperti izip_longestsolusi berbasis, yang di atas selalu bantalan. Sejauh yang saya tahu, tidak ada resep itertools satu atau dua baris untuk fungsi yang secara opsional membalut. Dengan menggabungkan dua pendekatan di atas, yang ini cukup dekat:

_no_padding = object()

def chunk(it, size, padval=_no_padding):
    if padval == _no_padding:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(padval))
        sentinel = (padval,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

Demo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
>>> list(chunk(range(14), 3, None))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Saya percaya ini adalah chunker terpendek yang diusulkan yang menawarkan padding opsional.

Seperti yang diamati oleh Tomasz Gandor , kedua chunker padding akan berhenti secara tak terduga jika mereka menemukan urutan panjang nilai pad. Berikut variasi terakhir yang mengatasi masalah itu dengan cara yang masuk akal:

_no_padding = object()
def chunk(it, size, padval=_no_padding):
    it = iter(it)
    chunker = iter(lambda: tuple(islice(it, size)), ())
    if padval == _no_padding:
        yield from chunker
    else:
        for ch in chunker:
            yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))

Demo:

>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]
pengirim
sumber
7
Luar biasa, versi sederhana Anda adalah favorit saya. Yang lain juga datang dengan islice(it, size)ekspresi dasar dan menyematkannya (seperti yang telah saya lakukan) dalam konstruksi lingkaran. Hanya Anda yang memikirkan versi dua argumen dari iter()(saya benar-benar tidak menyadari), yang membuatnya super elegan (dan mungkin paling efektif-kinerja). Saya tidak tahu bahwa argumen pertama untuk iterperubahan ke fungsi 0-argumen ketika diberi sentinel. Anda mengembalikan iterator (pot. Infinite) dari chunks, dapat menggunakan iterator (pot. Infinite) sebagai input, tidak memiliki len()dan tidak ada irisan array. Luar biasa!
ThomasH
1
Inilah sebabnya saya membaca jawaban daripada memindai hanya pasangan teratas. Padding opsional adalah persyaratan dalam kasus saya, dan saya juga belajar tentang bentuk dua argumen dari iter.
Kerr
Saya membatalkan ini, tapi tetap saja - jangan overhype! Pertama-tama, lambda bisa buruk (penutupan lambat atas ititerator. Kedua, dan yang paling penting - Anda akan berakhir sebelum waktunya jika sepotong padvalbenar - benar ada di iterable Anda, dan harus diproses.
Tomasz Gandor
@ ThomaszGandor, saya ambil poin pertama Anda! Meskipun pemahaman saya adalah lambda tidak lebih lambat dari fungsi biasa, tentu saja Anda benar bahwa pemanggilan fungsi dan penutupan akan memperlambat ini. Saya tidak tahu seperti apa kinerja relatif ini dibandingkan dengan izip_longestpendekatan, misalnya - saya kira itu mungkin kompromi yang rumit. Tapi ... bukankah padvalmasalah dibagikan oleh setiap jawaban di sini yang menawarkan padvalparameter?
pengirim
1
@ ThomaszGandor, cukup adil! Tapi itu tidak terlalu sulit untuk membuat versi yang memperbaiki ini. (Juga, perhatikan bahwa versi pertama, yang digunakan ()sebagai penjaga, tidak berfungsi dengan benar. Ini karena tuple(islice(it, size))menghasilkan ()ketika itkosong.)
pengirim
93

Berikut adalah generator yang bekerja pada iterables yang sewenang-wenang:

def split_seq(iterable, size):
    it = iter(iterable)
    item = list(itertools.islice(it, size))
    while item:
        yield item
        item = list(itertools.islice(it, size))

Contoh:

>>> import pprint
>>> pprint.pprint(list(split_seq(xrange(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]
Markus Jarderot
sumber
52
def chunk(input, size):
    return map(None, *([iter(input)] * size))
Tomasz Wysocki
sumber
map(None, iter)sama dengan izip_longest(iter).
Thomas Ahle
1
@TomaszWysocki Bisakah Anda menjelaskan *di depan Anda iterator tuple? Mungkin dalam teks jawaban Anda, tetapi saya perhatikan bahwa *menggunakan cara itu di Python sebelumnya. Terima kasih!
theJollySin
1
@theJollySin Dalam konteks ini, ini disebut operator percikan. Penggunaannya dijelaskan di sini - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms
2
Tutup tetapi potongan terakhir tidak memiliki elemen untuk mengisinya. Ini mungkin cacat atau tidak. Pola yang sangat keren.
49

Sederhana namun elegan

l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]

atau jika Anda lebih suka:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
lebenf
sumber
18
Jangan menjuluki variabel dalam bentuk angka Arab. Dalam beberapa font, 1dan ltidak bisa dibedakan. Apa adanya 0dan O. Dan terkadang bahkan Idan 1.
Alfe
14
@Fon Cacat yang rusak. Orang tidak boleh menggunakan font semacam itu. Bukan untuk pemrograman, tidak untuk apa pun .
Jerry B
17
Lambdas dimaksudkan untuk digunakan sebagai fungsi yang tidak disebutkan namanya. Tidak ada gunanya menggunakannya seperti itu. Selain itu, proses debug menjadi lebih sulit karena traceback akan melaporkan "di <lambda>" alih-alih "dalam potongan" jika terjadi kesalahan. Saya berharap Anda beruntung menemukan masalah jika Anda memiliki sejumlah ini :)
Chris Koston
1
seharusnya 0 dan bukan 1 di dalam xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta
CATATAN: Untuk pengguna Python 3 gunakan range.
Christian Dean
40

Kritik dari jawaban lain di sini:

Tidak satu pun dari jawaban ini yang berukuran rata, mereka semua meninggalkan potongan pada akhirnya, sehingga tidak sepenuhnya seimbang. Jika Anda menggunakan fungsi-fungsi ini untuk mendistribusikan pekerjaan, Anda sudah memiliki prospek yang mungkin diselesaikan jauh di depan yang lain, sehingga Anda tidak akan melakukan apa-apa sementara yang lain terus bekerja keras.

Misalnya, jawaban teratas saat ini berakhir dengan:

[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]

Aku hanya benci keruntuhan itu pada akhirnya!

Lainnya, seperti list(grouper(3, xrange(7))), dan chunk(xrange(7), 3)keduanya kembali: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Itu Nonehanya padding, dan agak tidak menurut saya. Mereka TIDAK secara merata memotong iterables.

Mengapa kita tidak bisa membagi ini lebih baik?

Solusi Saya

Berikut adalah solusi yang seimbang, diadaptasi dari fungsi yang saya gunakan dalam produksi (Catatan Python 3 untuk menggantikan xrangedengan range):

def baskets_from(items, maxbaskets=25):
    baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range
    for i, item in enumerate(items):
        baskets[i % maxbaskets].append(item)
    return filter(None, baskets) 

Dan saya membuat generator yang melakukan hal yang sama jika Anda memasukkannya ke dalam daftar:

def iter_baskets_from(items, maxbaskets=3):
    '''generates evenly balanced baskets from indexable iterable'''
    item_count = len(items)
    baskets = min(item_count, maxbaskets)
    for x_i in xrange(baskets):
        yield [items[y_i] for y_i in xrange(x_i, item_count, baskets)]

Dan akhirnya, karena saya melihat bahwa semua fungsi di atas mengembalikan elemen dalam urutan yang berdekatan (seperti yang diberikan):

def iter_baskets_contiguous(items, maxbaskets=3, item_count=None):
    '''
    generates balanced baskets from iterable, contiguous contents
    provide item_count if providing a iterator that doesn't support len()
    '''
    item_count = item_count or len(items)
    baskets = min(item_count, maxbaskets)
    items = iter(items)
    floor = item_count // baskets 
    ceiling = floor + 1
    stepdown = item_count % baskets
    for x_i in xrange(baskets):
        length = ceiling if x_i < stepdown else floor
        yield [items.next() for _ in xrange(length)]

Keluaran

Untuk mengujinya:

print(baskets_from(xrange(6), 8))
print(list(iter_baskets_from(xrange(6), 8)))
print(list(iter_baskets_contiguous(xrange(6), 8)))
print(baskets_from(xrange(22), 8))
print(list(iter_baskets_from(xrange(22), 8)))
print(list(iter_baskets_contiguous(xrange(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(xrange(26), 5))
print(list(iter_baskets_from(xrange(26), 5)))
print(list(iter_baskets_contiguous(xrange(26), 5)))

Yang mencetak:

[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'B', 'C'], ['D', 'E'], ['F', 'G']]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Perhatikan bahwa generator yang berdekatan memberikan potongan dalam pola panjang yang sama dengan dua lainnya, tetapi item semuanya dalam urutan, dan mereka dibagi secara merata karena orang dapat membagi daftar elemen diskrit.

Aaron Hall
sumber
Anda mengatakan bahwa tidak ada yang di atas memberikan potongan berukuran merata. Tapi yang ini , juga yang ini .
pengirim
1
@senderle, Yang pertama, list(grouper(3, xrange(7)))dan yang kedua, chunk(xrange(7), 3)keduanya kembali: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Itu Nonehanya padding, dan agak tidak menurut saya. Mereka TIDAK secara merata memotong iterables. Terima kasih atas penilaian Anda!
Aaron Hall
4
Anda mengajukan pertanyaan (tanpa melakukannya secara eksplisit, jadi saya melakukannya sekarang di sini) apakah potongan berukuran sama (kecuali yang terakhir, jika tidak memungkinkan) atau apakah hasil yang seimbang (sebaik mungkin) lebih sering merupakan apa yang dibutuhkan. Anda berasumsi bahwa solusi seimbang adalah memilih; ini mungkin benar jika apa yang Anda programkan dekat dengan dunia nyata (misalnya algoritma pengurusan kartu untuk permainan kartu simulasi). Dalam kasus lain (seperti mengisi baris dengan kata-kata) orang lebih suka untuk menjaga garis selebar mungkin. Jadi saya tidak bisa lebih suka yang satu daripada yang lain; mereka hanya untuk kasus penggunaan yang berbeda.
Alfe
@ ChristopherBarrington-Leigh Poin bagus, untuk DataFrames, Anda mungkin harus menggunakan irisan, karena saya percaya objek DataFrame biasanya tidak menyalin pada pemotongan, misalnyaimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall
1
@ AaronHall Ups. Saya menghapus komentar saya karena saya menebak-nebak kritik saya, tetapi Anda cepat dalam undian. Terima kasih! Sebenarnya, klaim saya bahwa itu tidak berfungsi untuk dataframe adalah benar. Jika item adalah kerangka data, gunakan saja item hasil [rentang (x_i, item_count, keranjang)] sebagai baris terakhir. Saya menawarkan jawaban yang terpisah (yang lain), di mana Anda menentukan ukuran grup (minimum) yang diinginkan.
CPBL
38

Saya melihat jawaban Python-ish yang paling mengagumkan dalam duplikat dari pertanyaan ini:

from itertools import zip_longest

a = range(1, 16)
i = iter(a)
r = list(zip_longest(i, i, i))
>>> print(r)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

Anda dapat membuat n-tuple untuk n apa pun. Jika a = range(1, 15), maka hasilnya adalah:

[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]

Jika daftar ini dibagi secara merata, maka Anda dapat mengganti zip_longestdengan zip, jika tidak, triplet (13, 14, None)akan hilang. Python 3 digunakan di atas. Untuk Python 2, gunakan izip_longest.

Noich
sumber
itu bagus jika daftar dan potongan Anda pendek, bagaimana Anda bisa mengadaptasi ini untuk membagi daftar Anda menjadi potongan 1000? Anda tidak akan kode pos (saya, saya, saya, saya, saya, saya, saya, saya, saya, saya ..... saya = 1000)
Tom Smith
9
zip(i, i, i, ... i)dengan argumen "chunk_size" ke zip () dapat ditulis sebagai zip(*[i]*chunk_size)Apakah itu ide yang bagus atau tidak bisa diperdebatkan, tentu saja.
Wilson F
1
Kelemahan dari ini adalah bahwa jika Anda tidak membagi secara merata, Anda akan menjatuhkan elemen, karena zip berhenti di iterable terpendek - & izip_longest akan menambahkan elemen default.
Aaron Hall
zip_longestharus digunakan, seperti yang dilakukan di: stackoverflow.com/a/434411/1959808
Ioannis Filippidis
Jawaban dengan range(1, 15)elemen yang sudah hilang, karena ada 14 elemen range(1, 15), bukan 15.
Ioannis Filippidis
35

Jika Anda tahu ukuran daftar:

def SplitList(mylist, chunk_size):
    return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]

Jika Anda tidak (iterator):

def IterChunks(sequence, chunk_size):
    res = []
    for item in sequence:
        res.append(item)
        if len(res) >= chunk_size:
            yield res
            res = []
    if res:
        yield res  # yield the last, incomplete, portion

Dalam kasus yang terakhir, ini dapat diulang dengan cara yang lebih indah jika Anda bisa yakin bahwa urutannya selalu berisi sejumlah bongkahan dengan ukuran tertentu (yaitu tidak ada potongan terakhir yang tidak lengkap).

atzz
sumber
Saya sedih ini terkubur sejauh ini. The IterChunks bekerja untuk semuanya dan merupakan solusi umum dan tidak memiliki peringatan yang saya ketahui.
Jason Dunkelberger
18

The Toolz perpustakaan memiliki partitionfungsi untuk ini:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
zach
sumber
Ini terlihat seperti saran yang paling sederhana. Saya hanya ingin tahu apakah memang benar bahwa seseorang harus menggunakan perpustakaan pihak ketiga untuk mendapatkan fungsi partisi seperti itu. Saya akan mengharapkan sesuatu yang setara dengan fungsi partisi yang ada sebagai bahasa builtin.
kasperd
1
Anda dapat melakukan partisi dengan itertools. tapi saya suka perpustakaan toolz. ini adalah perpustakaan yang terinspirasi oleh clojure untuk mengerjakan koleksi dengan gaya fungsional. Anda tidak mendapatkan kekekalan tetapi Anda mendapatkan kosakata kecil untuk mengerjakan koleksi sederhana. Sebagai nilai tambah, cytoolz ditulis dalam cython dan mendapat peningkatan kinerja yang bagus. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/
zach
Tautan dari komentar zach berfungsi jika Anda menghentikan
mit
17

Jika Anda memiliki ukuran chunk 3 misalnya, Anda bisa melakukannya:

zip(*[iterable[i::3] for i in range(3)]) 

sumber: http://code.activestate.com/recipes/303060-group-a-list-into- berikutnyaential-n-tuples/

Saya akan menggunakan ini ketika ukuran chunk saya adalah angka tetap yang dapat saya ketik, misalnya '3', dan tidak akan pernah berubah.

ninjagecko
sumber
11
Ini tidak berfungsi jika len (iterable)% 3! = 0. Kelompok angka terakhir (pendek) tidak akan dikembalikan.
sherbang
16

Saya suka versi dokumen Python yang diusulkan oleh tzot dan JFSebastian, tetapi memiliki dua kekurangan:

  • itu tidak terlalu eksplisit
  • Saya biasanya tidak ingin nilai isian di bagian terakhir

Saya banyak menggunakan ini dalam kode saya:

from itertools import islice

def chunks(n, iterable):
    iterable = iter(iterable)
    while True:
        yield tuple(islice(iterable, n)) or iterable.next()

UPDATE: Versi potongan malas:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))
nikipore
sumber
Apa kondisi istirahat untuk while Trueloop?
wjandrea
@wjandrea: Dimunculkan StopIterationketika tuplekosong dan iterable.next()dieksekusi. Tidak bekerja dengan baik di Python modern, di mana keluar generator harus dilakukan return, bukan menaikkan StopIteration. A di try/except StopIteration: returnsekitar seluruh loop (dan berubah iterable.next()menjadi next(iterable)untuk versi lintas versi) memperbaikinya dengan overhead minimal.
ShadowRanger
15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Di mana AA adalah array, SS adalah ukuran chunk. Sebagai contoh:

>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3
Riaz Rizvi
sumber
2
ini yang terbaik dan sederhana.
F.Tamy
2
pendek dan sederhana. kesederhanaan atas kompleksitas.
dkrynicki
15

Saya ingin tahu tentang kinerja berbagai pendekatan dan ini dia:

Diuji pada Python 3.5.1

import time
batch_size = 7
arr_len = 298937

#---------slice-------------

print("\r\nslice")
start = time.time()
arr = [i for i in range(0, arr_len)]
while True:
    if not arr:
        break

    tmp = arr[0:batch_size]
    arr = arr[batch_size:-1]
print(time.time() - start)

#-----------index-----------

print("\r\nindex")
arr = [i for i in range(0, arr_len)]
start = time.time()
for i in range(0, round(len(arr) / batch_size + 1)):
    tmp = arr[batch_size * i : batch_size * (i + 1)]
print(time.time() - start)

#----------batches 1------------

def batch(iterable, n=1):
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

print("\r\nbatches 1")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#----------batches 2------------

from itertools import islice, chain

def batch(iterable, size):
    sourceiter = iter(iterable)
    while True:
        batchiter = islice(sourceiter, size)
        yield chain([next(batchiter)], batchiter)


print("\r\nbatches 2")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#---------chunks-------------
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]
print("\r\nchunks")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in chunks(arr, batch_size):
    tmp = x
print(time.time() - start)

#-----------grouper-----------

from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(iterable, n, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

arr = [i for i in range(0, arr_len)]
print("\r\ngrouper")
start = time.time()
for x in grouper(arr, batch_size):
    tmp = x
print(time.time() - start)

Hasil:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844
Alex T
sumber
3
benchmarking menggunakan timeperpustakaan bukanlah ide bagus ketika kita memiliki timeitmodul
Azat Ibrakov
13

kode:

def split_list(the_list, chunk_size):
    result_list = []
    while the_list:
        result_list.append(the_list[:chunk_size])
        the_list = the_list[chunk_size:]
    return result_list

a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print split_list(a_list, 3)

hasil:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Seni B
sumber
12

Anda juga dapat menggunakan get_chunksfungsi utilspieperpustakaan sebagai:

>>> from utilspie import iterutils
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(iterutils.get_chunks(a, 5))
[[1, 2, 3, 4, 5], [6, 7, 8, 9]]

Anda dapat menginstal utilspiemelalui pip:

sudo pip install utilspie

Penafian: Saya adalah pencipta perpustakaan utilspie .

Moinuddin Quadri
sumber
11

Pada titik ini, saya pikir kita perlu generator rekursif , untuk berjaga-jaga ...

Dengan python 2:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

Dengan python 3:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    yield from chunks(li[n:], n)

Juga, dalam kasus invasi Alien besar-besaran, generator rekursif yang dihiasi mungkin menjadi berguna:

def dec(gen):
    def new_gen(li, n):
        for e in gen(li, n):
            if e == []:
                return
            yield e
    return new_gen

@dec
def chunks(li, n):
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e
mazieres
sumber
9

Dengan Ekspresi Penugasan dalam Python 3.8 menjadi cukup bagus:

import itertools

def batch(iterable, size):
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item

Ini berfungsi pada iterable sewenang-wenang, bukan hanya daftar.

>>> import pprint
>>> pprint.pprint(list(batch(range(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]
nirvana-msu
sumber
1
Sekarang ini adalah jawaban baru yang layak untuk pertanyaan ini. Sebenarnya saya cukup suka ini. Saya skeptis terhadap ekspresi tugas, tetapi ketika mereka bekerja mereka bekerja.
juanpa.arrivillaga
7

heh, satu versi baris

In [48]: chunk = lambda ulist, step:  map(lambda i: ulist[i:i+step],  xrange(0, len(ulist), step))

In [49]: chunk(range(1,100), 10)
Out[49]: 
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]
slav0nic
sumber
36
Tolong, gunakan "def chunk" bukannya "chunk = lambda". Itu bekerja sama. Satu baris Fitur yang sama. JAUH lebih mudah ke n00bz untuk membaca dan memahami.
S.Lott
4
@ S.Lott: tidak jika n00bz berasal dari skema: P ini bukan masalah nyata. bahkan ada kata kunci untuk google! fitur apa lagi yang kami tampilkan untuk menghindari n00bz? Saya kira hasil tidak cukup penting / c-seperti untuk menjadi ramah n00b juga.
Janus Troelsen
16
Objek fungsi yang dihasilkan dari def chunkalih-alih chunk=lambdamemiliki .__ name__ atribut 'chunk' alih-alih '<lambda>'. Nama spesifik lebih berguna di traceback.
Terry Jan Reedy
1
@Alfe: Saya tidak yakin apakah bisa disebut sebagai perbedaan semantik utama, tetapi apakah ada nama yang berguna dalam traceback alih-alih <lamba>atau tidak, paling tidak, perbedaan penting.
martineau
1
Setelah menguji banyak dari mereka untuk kinerja, INI Hebat!
Sunny Patel
7
def split_seq(seq, num_pieces):
    start = 0
    for i in xrange(num_pieces):
        stop = start + len(seq[i::num_pieces])
        yield seq[start:stop]
        start = stop

pemakaian:

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for seq in split_seq(seq, 3):
    print seq
Corey Goldberg
sumber
7

Versi lain yang lebih eksplisit.

def chunkList(initialList, chunkSize):
    """
    This function chunks a list into sub lists 
    that have a length equals to chunkSize.

    Example:
    lst = [3, 4, 9, 7, 1, 1, 2, 3]
    print(chunkList(lst, 3)) 
    returns
    [[3, 4, 9], [7, 1, 1], [2, 3]]
    """
    finalList = []
    for i in range(0, len(initialList), chunkSize):
        finalList.append(initialList[i:i+chunkSize])
    return finalList
Ranaivo
sumber
(2016 Sep 12) Jawaban ini adalah bahasa yang paling mandiri dan paling mudah dibaca.
D Adams
7

Tanpa memanggil len () yang bagus untuk daftar besar:

def splitter(l, n):
    i = 0
    chunk = l[:n]
    while chunk:
        yield chunk
        i += n
        chunk = l[i:i+n]

Dan ini untuk iterables:

def isplitter(l, n):
    l = iter(l)
    chunk = list(islice(l, n))
    while chunk:
        yield chunk
        chunk = list(islice(l, n))

Rasa fungsional di atas:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(l))))

ATAU:

def chunks_gen_sentinel(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return iter(imap(tuple, continuous_slices).next,())

ATAU:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))
Mars
sumber
16
Tidak ada alasan untuk menghindari len()daftar besar; ini adalah operasi waktu konstan.
Thomas Wouters
7

Berikut adalah daftar pendekatan tambahan:

Diberikan

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Kode

Perpustakaan Standar

list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

d = {}
for i, x in enumerate(iterable):
    d.setdefault(i//n, []).append(x)

list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
    dd[i//n].append(x)

list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

more_itertools+

list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]

list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

Referensi

+ Perpustakaan pihak ketiga yang mengimplementasikan resep itertools dan banyak lagi.> pip install more_itertools

pylang
sumber
6

Lihat referensi ini

>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>> 

Python3

macm
sumber
3
Bagus, tetapi menjatuhkan elemen pada akhirnya jika ukurannya tidak cocok dengan bilangan bulat keseluruhan, mis. zip(*[iter(range(7))]*3)Hanya mengembalikan [(0, 1, 2), (3, 4, 5)]dan melupakan 6dari input.
Alfe
6

Karena semua orang di sini berbicara tentang iterator. boltonsmemiliki metode sempurna untuk itu, disebut iterutils.chunked_iter.

from boltons import iterutils

list(iterutils.chunked_iter(list(range(50)), 11))

Keluaran:

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

Tapi jika Anda tidak ingin menjadi rahmat pada memori, Anda dapat menggunakan tua-cara dan menyimpan penuh listdi tempat pertama dengan iterutils.chunked.

vishes_shell
sumber
Dan yang ini benar-benar berfungsi terlepas dari urutan orang melihat subiterator !!
Peter Gerdes
6

Satu lagi solusi

def make_chunks(data, chunk_size): 
    while data:
        chunk, data = data[:chunk_size], data[chunk_size:]
        yield chunk

>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
...     print chunk
... 
[1, 2]
[3, 4]
[5, 6]
[7]
>>> 
Анатолий Панин
sumber
5
def chunks(iterable,n):
    """assumes n is an integer>0
    """
    iterable=iter(iterable)
    while True:
        result=[]
        for i in range(n):
            try:
                a=next(iterable)
            except StopIteration:
                break
            else:
                result.append(a)
        if result:
            yield result
        else:
            break

g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'
raja robert
sumber
1
Walaupun ini mungkin tidak terlihat sependek atau secantik banyak respons berbasis itertools ini benar-benar berfungsi jika Anda ingin mencetak sub-daftar kedua sebelum mengakses yang pertama, yaitu, Anda dapat mengatur i0 = next (g2); i1 = berikutnya (g2); dan gunakan i1 sebelum menggunakan i0 dan tidak rusak !!
Peter Gerdes
5

Pertimbangkan untuk menggunakan potongan matplotlib.cbook

sebagai contoh:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
schwater
sumber
Sepertinya Anda tidak sengaja membuat dua akun. Anda dapat menghubungi tim untuk menggabungkan mereka, yang akan memungkinkan Anda untuk mendapatkan kembali hak mengedit langsung pada kontribusi Anda.
Georgy