Pytanie W jaki sposób podzielisz listę na kawałki o równej wielkości?
Mam listę dowolnej długości i muszę podzielić ją na części o jednakowej wielkości i operować na niej. Są na to oczywiste sposoby, na przykład zachowanie licznika i dwóch list, a kiedy druga lista się wypełni, dodaj ją do pierwszej listy i opróżnij drugą listę dla następnej rundy danych, ale jest to potencjalnie bardzo kosztowne.
Zastanawiałem się, czy ktoś ma dobre rozwiązanie tego w przypadku list o dowolnej długości, np. używanie generatorów.
Szukałem czegoś pożytecznego itertools
ale nie mogłem znaleźć niczego, co byłoby oczywiście użyteczne. Może jednak tęskniłem za tym.
Powiązane pytanie: Jaki jest najbardziej "pythonic" sposób iteracji na liście w kawałkach?
1580
2017-11-23 12:15
pochodzenie
Odpowiedzi:
Oto generator, który dostarcza żądanych porcji:
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]]
Jeśli używasz Pythona 2, powinieneś użyć xrange()
zamiast 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]
Możesz także po prostu użyć funkcji czytania ze zrozumieniem list zamiast pisania funkcji. Python 3:
[l[i:i + n] for i in range(0, len(l), n)]
Wersja Python 2:
[l[i:i + n] for i in xrange(0, len(l), n)]
2116
2017-11-23 12:33
Jeśli chcesz coś super prostego:
def chunks(l, n):
n = max(1, n)
return (l[i:i+n] for i in xrange(0, len(l), n))
481
2017-11-17 20:17
Bezpośrednio z (starej) dokumentacji Pythona (przepisy na 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)
Obecna wersja, jak zasugerował 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)
Zgaduję, że wehikuł czasu Guido działa - pracował - będzie pracował - będzie pracował - znowu pracował.
Te rozwiązania działają, ponieważ [iter(iterable)]*n
(lub odpowiednik we wcześniejszej wersji) tworzy jeden iterator, powtórzono n
razy na liście. izip_longest
następnie efektywnie wykonuje round-robin "każdego" iteratora; ponieważ jest to ten sam iterator, jest zaawansowany przez każde takie wywołanie, w wyniku czego każdy taki zip-roundrobin generuje jedną krotkę n
przedmiotów.
251
2017-11-23 15:48
Wiem, że to trochę stare, ale nie wiem dlaczego nikt o tym nie wspomniał numpy.array_split
:
lst = range(50)
In [26]: np.array_split(lst,5)
Out[26]:
[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])]
94
2018-06-05 08:54
Oto generator, który działa na dowolnych iterabelach:
def split_seq(iterable, size):
it = iter(iterable)
item = list(itertools.islice(it, size))
while item:
yield item
item = list(itertools.islice(it, size))
Przykład:
>>> 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]]
79
2017-11-23 12:41
Jestem zaskoczony, że nikt nie pomyślał o użyciu iter
„s dwuargumentowa forma:
from itertools import islice
def chunk(it, size):
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
Próbny:
>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
Działa to z każdą iteracją i produkuje leniwie. Zwraca krotki zamiast iteratorów, ale myślę, że ma jednak pewną elegancję. To także nie pada; jeśli chcesz dopełnić, wystarczy jedna prosta odmiany:
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)
Próbny:
>>> 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')]
Jak izip_longest
rozwiązania oparte na powyższym zawsze naramienniki. O ile mi wiadomo, nie ma jedno- lub dwuliniowej receptury itertools dla funkcji, która opcjonalnie naramienniki. Łącząc powyższe dwa podejścia, ten jest dość bliski:
_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)
Próbny:
>>> 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')]
Uważam, że jest to najkrótsza propozycja, która oferuje opcjonalne wypełnienie.
65
2018-02-26 15:02
def chunk(input, size):
return map(None, *([iter(input)] * size))
47
2018-06-26 19:10