Saya memiliki daftar panjang sewenang-wenang, dan saya perlu untuk membaginya menjadi potongan ukuran yang sama dan beroperasi pada itu. Ada beberapa cara yang jelas untuk melakukan hal ini, seperti menjaga sebuah meja dan dua daftar, dan ketika kedua daftar mengisi, menambahkannya ke daftar pertama dan kosong kedua daftar untuk putaran berikutnya dari data, tapi ini berpotensi sangat mahal.
Aku bertanya-tanya apakah ada yang punya solusi yang baik untuk ini untuk daftar dari setiap panjang, misalnya menggunakan generator.
Aku sedang mencari sesuatu yang berguna di itertools
tapi aku tidak't menemukan sesuatu yang jelas-jelas berguna. Mungkin've kehilangan itu, meskipun.
Terkait pertanyaan: Apa yang paling "pythonic" cara untuk iterate atas daftar dalam potongan?
Berikut ini's sebuah generator yang menghasilkan potongan yang anda inginkan:
def chunks(l, n):
"""Yield successive n-sized chunks from l."""
for i in range(0, len(l), n):
yield l[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're menggunakan Python 2, anda harus menggunakan xrange()
bukan range()
:
def chunks(l, n):
"""Yield successive n-sized chunks from l."""
for i in xrange(0, len(l), n):
yield l[i:i + n]
Anda juga dapat hanya menggunakan daftar pemahaman alih-alih menulis fungsi, meskipun itu's merupakan ide yang baik untuk merangkum operasi seperti ini di bernama fungsi sehingga kode anda lebih mudah untuk memahami. Python 3:
[l[i:i + n] for i in range(0, len(l), n)]
Python versi 2:
[l[i:i + n] for i in xrange(0, len(l), n)]
Langsung dari (lama) Python dokumentasi (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 J. F. Sebastian:
#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 Guido's waktu mesin bekerja—bekerja—akan bekerja—bekerja—bekerja lagi.
Solusi ini bekerja karena [iter(iterable)]*n
(atau setara dalam versi sebelumnya) menciptakan satu iterator, diulang n
kali dalam daftar. izip_longest
maka secara efektif melakukan round-robin dari "setiap orang" iterator; karena ini adalah sama iterator, itu maju dengan setiap panggilan tersebut, sehingga masing-masing seperti zip-roundrobin menghasilkan satu tupel dari n
barang-barang.
Saya tahu ini adalah jenis dari lama tapi belum ada yang disebutkan 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])]
I'm heran tidak ada yang berpikir untuk menggunakan iter
's dua bentuk 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 malas. Kembali tupel daripada iterator, tapi saya pikir ia memiliki keanggunan tertentu tetap. Hal ini juga doesn't pad; jika anda ingin padding, variasi sederhana di atas akan 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_longest
berbasis solusi di atas always bantalan. Sejauh yang saya tahu, ada's tidak ada satu atau dua baris itertools resep untuk fungsi yang optionally bantalan. Dengan menggabungkan dua pendekatan di atas, yang satu ini datang 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 yang terpendek chunker diusulkan yang menawarkan opsional padding.
Sebagai Tomasz Gandor observed, dua padding chunkers akan berhenti secara tiba-tiba jika mereka menemukan urutan panjang dari nilai-nilai pad. Berikut ini's final variasi yang bekerja di sekitar masalah itu dengan cara yang wajar:
_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)]
Berikut ini adalah generator yang bekerja pada sewenang-wenang iterables:
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]]
Saya melihat yang paling mengagumkan Python-ish jawaban di 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 setiap n. Jika a = range(1, 15)
, maka hasilnya akan menjadi:
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]
Jika daftar ini dibagi secara merata, kemudian anda dapat mengganti zip_longest
dengan zip
, jika tidak triplet (13, 14, Tidak ada)
akan hilang. Python 3 digunakan di atas. Untuk Python 2, menggunakan izip_longest
.
Tidak satupun dari jawaban-jawaban ini merata ukuran potongan, mereka semua meninggalkan kerdil chunk di akhir, sehingga mereka're tidak benar-benar seimbang. Jika anda menggunakan fungsi ini untuk mendistribusikan pekerjaan, anda've built-in prospek dari satu kemungkinan finishing yang baik di hadapan orang lain, sehingga akan duduk-duduk melakukan apa-apa sementara yang lain terus bekerja keras.
Misalnya, saat ini atas jawabannya berakhir dengan:
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]
Aku hanya benci yang kerdil di akhir!
Yang lain, seperti daftar(kerapu(3, xrange(7)))
, dan chunk(xrange(7), 3)
kembali: [(0, 1, 2), (3, 4, 5), (6, Tidak ada, Tidak ada)]
. None
's hanya padding, dan agak janggal menurut saya. Mereka yang TIDAK merata chunking iterables.
Mengapa bisa't kita membagi ini lebih baik?
Berikut ini's solusi seimbang, diadaptasi dari fungsi I've yang digunakan dalam produksi (Catatan di Python 3 untuk menggantikan xrange
dengan 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 sebuah generator yang melakukan hal yang sama jika anda memasukkannya ke 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 kembali unsur-unsur dalam yang bersebelahan order (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)]
Untuk menguji mereka:
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 bersebelahan generator memberikan potongan sama panjang pola seperti dua lainnya, tapi item semua dalam rangka, dan mereka yang secara merata dibagi sebagai salah satu mungkin membagi daftar elemen diskrit.
Jika anda tahu daftar ukuran:
def SplitList(mylist, chunk_size):
return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]
Jika anda don't (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 kedua kasus, hal ini dapat diulang dalam lebih indah jika anda dapat yakin bahwa urutan selalu mengandung seluruh jumlah potongan dari ukuran tertentu (yaitu tidak ada yang tidak lengkap terakhir chunk).
Jika anda memiliki sepotong ukuran 3 misalnya, anda bisa melakukan:
zip(*[iterable[i::3] for i in range(3)])
sumber: http://code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/
Aku akan menggunakan ini ketika saya chunk size adalah jumlah tetap saya bisa mengetik, misalnya '3', dan tidak akan pernah berubah.
The toolz perpustakaan memiliki partisi
fungsi untuk ini:
from toolz.itertoolz.core import partition
list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
Aku suka Python doc's versi diusulkan oleh tzot dan J. F. Sebastian banyak, tapi ini memiliki dua kelemahan:
I'm menggunakan yang satu ini banyak di kode saya:
from itertools import islice
def chunks(n, iterable):
iterable = iter(iterable)
while True:
yield tuple(islice(iterable, n)) or iterable.next()
UPDATE: malas potongan versi:
from itertools import chain, islice
def chunks(n, iterable):
iterable = iter(iterable)
while True:
yield chain([next(iterable)], islice(iterable, n-1))
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)
hasilnya:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Saya penasaran tentang kinerja pendekatan yang berbeda dan di sini adalah:
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
Pada titik ini, saya pikir kita perlu rekursif generator, hanya dalam kasus...
Di python versi 2:
def chunks(li, n):
if li == []:
return
yield li[:n]
for e in chunks(li[n:], n):
yield e
Di python 3:
def chunks(li, n):
if li == []:
return
yield li[:n]
yield from chunks(li[n:], n)
Juga, dalam kasus invasi Alien besar, a dihiasi rekursif generator 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
Anda juga dapat menggunakan get_chunks
fungsi utilspie
perpustakaan 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 utilspie
melalui pip:
sudo pip install utilspie
Disclaimer: saya pencipta utilspie perpustakaan.
[AA[i:i+SS] for i in range(len(AA))[::SS]]
Di mana AA adalah array, SS adalah ukuran potongan. Misalnya:
>>> 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
heh, satu line versi
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]]