Bagaimana anda membagi daftar ke merata berukuran potongan?

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?

Larutan

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)]
Komentar (10)

Jika anda ingin sesuatu yang super sederhana:

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

Gunakan range() bukan xrange() dalam kasus Python 3.x

Komentar (6)

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.

Komentar (9)

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])]
Komentar (6)

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_longestberbasis 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)]
Komentar (8)

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]]
Komentar (0)
def chunk(input, size):
    return map(None, *([iter(input)] * size))
Komentar (4)

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:

chunks = lambda l, n: [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
Komentar (5)

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.

Komentar (5)

Kritik dari jawaban yang lain di sini:

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?

Solusi saya(s)

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

Output

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.

Komentar (8)

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

Komentar (1)

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.

Komentar (1)

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)]
Komentar (3)

Aku suka Python doc's versi diusulkan oleh tzot dan J. F. Sebastian banyak, tapi ini memiliki dua kelemahan:

  • hal ini tidak sangat eksplisit
  • Biasanya saya don't ingin mengisi nilai terakhir chunk

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))
Komentar (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]]
Komentar (0)

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
Komentar (1)

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
Komentar (0)

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.

Komentar (1)
[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
Komentar (1)

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]]
Komentar (5)