Cum vă împărțiți lista în mod uniform bucăți de dimensiuni?

Am o listă de lungime arbitrară, și trebuie să-l împărțim în bucăți de dimensiuni egale și opera. Există unele moduri evidente de a face acest lucru, ca și cum ai ține un contor și două liste, și atunci când cea de-a doua listă se umple, adăugați-l la prima listă și gol de-a doua listă pentru următoarea rundă de date, dar acest lucru poate fi extrem de costisitoare.

Mă întrebam dacă cineva ar fi o soluție bună pentru această pentru liste de orice lungime, de exemplu, folosind generatoare.

Am fost în căutarea pentru ceva util în itertools` dar nu puteam't găsi nimic în mod evident utile. S-ar putea'am ratat-o, totuși.

Intrebare legate de: ceea Ce este cel mai "pythonic" mod de a repeta peste o listă în bucăți?

Soluția

Aici's un generator care produce bucăți vrei:

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

Daca're folosind Python 2, ar trebui să utilizați xrange () "în loc de" 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]

De asemenea, puteți folosi pur și simplu lista de înțelegere în loc de a scrie o funcție, deși l's o idee bună pentru a îngloba astfel de operațiuni în nume de funcții, astfel încât codul este mai ușor de înțeles. Python 3:

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

Python 2 versiune:

[l[i:i + n] for i in xrange(0, len(l), n)]
Comentarii (10)

Daca vrei ceva super simplu:

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

Folosesc gama () "în loc de" xrange()` în cazul Python 3.x

Comentarii (6)

Direct de la (vechi) Python documentare (retete pentru 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)

Versiunea actuală, cum a sugerat de către 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)

Cred că Guido's time machine funcționează—lucrat—va locul de muncă—va fi lucrat—a fost din nou de lucru.

Aceste soluții de lucru pentru că [iter(iterable)]*n (sau echivalentul în versiunea anterioară) creează o iterator, repetate de " n "ori în listă. izip_longest apoi efectuează în mod eficient un round-robin de "fiecare" iterator; pentru că aceasta este aceeași iterator, este avansat de către fiecare astfel de apel, care rezultă în fiecare astfel de zip-roundrobin generând un tuplu din" n " elemente.

Comentarii (9)

Știu că acest lucru este la fel de vechi, dar nimeni nu a menționat încă 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])]
Comentarii (6)

Am'm-a surprins nimeni nu a crezut de a folosi iter's doi-argument forma:

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

Aceasta funcționează cu orice iterable și produce ieșire alene. Se întoarce tupluri, mai degrabă decât iteratori, dar cred că are o anumită eleganță cu toate acestea. De asemenea, nu't pad; dacă vrei umplutură, o simplă variație pe cele de mai sus va fi suficient:

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

Ca izip_longestbazate pe soluții, mai sus always tampoane. După câte știu eu, nu's nu una sau două linii itertools reteta pentru o funcție care optionally tampoane. Prin combinarea celor două abordări de mai sus, acesta vine destul de aproape:

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

Cred că acest lucru este cel mai scurt chunker propuse, care oferă opțional umplutură.

Ca Tomasz Gandor observedcele două umplutură chunkers va opri în mod neașteptat dacă vor întâlni o secvență lungă de pad valori. Aici's o ultimă variantă care funcționează în jurul valorii de această problemă într-un mod rezonabil:

_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)]
Comentarii (8)

Aici este un generator care funcționează pe arbitrare 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))

Exemplu:

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

Simplu, dar elegant

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

sau, dacă preferați:

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

Am văzut cel mai minunat Python-ish răspuns într-un duplicat de la această întrebare:

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

Puteți crea n-tuplu pentru orice n. Dacă o = range(1, 15), atunci rezultatul va fi:

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

Dacă lista este împărțită în mod egal, atunci puteți înlocui zip_longest cu "zip", în caz contrar triplet(13, 14, Niciuna)ar fi pierdut. Python 3 este utilizat de mai sus. Pentru Python 2, folosesc izip_longest.

Comentarii (5)

Critica de alte raspunsuri aici:

Nici unul dintre aceste răspunsuri sunt uniform bucăți de dimensiuni, toate acestea lasă un runt bucată la sfârșitul anului, astfel încât acestea're nu este complet echilibrat. Dacă ați fost utilizați aceste funcții pentru a distribui munca, ai've built-in perspectiva un susceptibile de finisare bine în fața celorlalți, așa că ar sta în jurul valorii de a nu face nimic în timp ce alții au continuat să lucreze din greu.

De exemplu, actualul top răspuns se termină cu:

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

Nu-mi place că runt la sfârșitul!

Altele, cum ar fi lista(grouper(3, xrange(7)))", și " bucată(xrange(7), 3)întoarce amândoi:[(0, 1, 2), (3, 4, 5), (6, Nici unul, Nici una)]`. "Nici unul" 's sunt doar de umplutură, și destul de elegant, în opinia mea. Ele NU sunt uniform chunking la iterables.

De ce't vom împărți aceste mai bine?

Soluția mea(s)

Aici's o soluție echilibrată, adaptată la o funcție I've utilizate în producție (a se reține în Python 3 pentru a înlocui `xrange cu "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) 

Și am creat un generator care face acelasi lucru, dacă l-ai pus într-o listă:

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

Și, în sfârșit, când am văzut că toate funcțiile de mai sus se întoarcă elemente într-un învecinate ordine (ca s-au dat):

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

Ieșire

Pentru a le testa:

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

Care imprimă:

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

Observați că învecinate generator oferi în bucăți de aceeași lungime modele ca celelalte două, dar elementele sunt toate în ordine, și ei sunt la fel de uniform împărțită după cum se poate împărți o listă de elemente discrete.

Comentarii (8)

Dacă știți dimensiunea listei:

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

Dacă tu nu't (un 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

În acest din urmă caz, acesta poate fi reformulat într-un mod mai frumos dacă puteți fi sigur că secvența conține întotdeauna un număr întreg de bucăți de dimensiune dată (de exemplu, nu există nici incomplete ultima bucată).

Comentarii (1)

Dacă ai avut o dimensiune bucată de 3 de exemplu, ai putea face:

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

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

Mi-ar folosi acest lucru atunci când mi bucată dimensiune este număr fix nu pot tip, de exemplu '3', și nu se va schimba niciodată.

Comentarii (1)

De toolz biblioteca are `partiție funcția pentru acest lucru:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
Comentarii (3)

Îmi place Python doc's version propus de tzot și J. F. Sebastian o mulțime, dar are două neajunsuri:

  • nu este foarte explicit
  • Eu de obicei nu't doresc o umple de valoare în ultima bucată

Am'm, folosind o mulțime în codul meu:

from itertools import islice

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

UPDATE: UN leneș bucăți versiune:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))
Comentarii (1)

cod:

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)

rezultatul:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Comentarii (0)

Am fost curios cu privire la performanța de abordări diferite și aici este:

Testat pe 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)

Rezultate:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844
Comentarii (1)

La acest moment, cred că avem nevoie de un recursiv generator, doar în cazul în...

În python 2:

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

În python 3:

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

De asemenea, în caz de invazie Extraterestră masiv, un decorat recursiv generator ar putea deveni la îndemână:

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

De asemenea, puteți utiliza get_chunks funcția de utilspie biblioteca fi:

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

Puteți instala utilspie prin pip:

sudo pip install utilspie

Disclaimer: eu sunt creatorul utilspie biblioteca.

Comentarii (1)
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Unde AA este matrice, SS este de dimensiune bucată. De exemplu:

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

heh, o versiune linie de

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