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


Zoptymalizowane rozwiązanie (bardziej przyjazne dla pamięci) tutaj: stackoverflow.com/questions/7133179/python-yield-and-delete - Radim
FWIW, biblioteka more_itertools oferuje a chunked funkcja, która robi to w skuteczny sposób. - bgusach


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



Co się stanie, jeśli nie będziemy w stanie podać długości listy? Wypróbuj to na itertools.repeat ([1, 2, 3]), np. - jespern
To interesujące pytanie, ale pierwotne pytanie wyraźnie dotyczyło pracy na liście. - Ned Batchelder
Program portujący 2to3 zmienia wszystkie wywołania xrange na zakres, ponieważ w Pythonie 3.0 funkcjonalność zakresu będzie równoważna zakresowi xrange (to znaczy zwróci iterator). Tak więc unikałbym używania zakresu i używania xrange zamiast tego. - Tomi Kyöstilä
@attz faktycznie range został usunięty z Pythona 3.0 i xrange zmienił nazwę na range. - Kos
@zedr, że "rozumienie krotki" jest w rzeczywistości "ekspresją generatora". Zrozumienie krotki byłoby bardziej podobne tuple(l[i:i+n] for i in xrange(0, len(l), n)). :-) - Ben Hoyt


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



Lub (jeśli robimy różne reprezentacje tej konkretnej funkcji), możesz zdefiniować funkcję lambda poprzez: lambda x, y: [x [i: i + y] dla i w zakresie (0, len (x), y) ]. Uwielbiam tę metodę rozumienia listy! - J-P
po powrocie musi być [, nie ( - alwbtc
@alwbtc - nie, to prawda, to generator - Mr_and_Mrs_D
"Super prosty" oznacza brak konieczności debugowania nieskończonych pętli - kudos dla max(). - Bob Stein


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



To jest izip_longest(*[iter(iterable)]*n, fillvalue=fillvalue) dzisiaj. - jfs
przegłosowano to, ponieważ działa na generatorach (bez len) i wykorzystuje ogólnie szybszy moduł itertools. - Michael Dillon
Możesz połączyć to wszystko w krótki jednolinijkowy: zip(*[iter(yourList)]*n) (lub izip_longest z wartością wypełnienia) - ninjagecko
Klasyczny przykład fantazji itertools podejście funkcjonalne polegające na wykryciu nieczytelnego szlamu w porównaniu z prostą i naiwną implementacją czystego Pythona - wim
@wim Biorąc pod uwagę, że ta odpowiedź zaczęła się od fragmentu dokumentacji Pythona, proponuję otworzyć problem bugs.python.org . - tzot


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



Pozwala to ustawić całkowitą liczbę porcji, a nie liczbę elementów na porcję. - FizxMike
możesz sam zrobić matematykę. jeśli masz 10 elementów, możesz podzielić je na 2, 5 elementów lub pięć elementów 2-elementowych - Moj
+1 To jest moje ulubione rozwiązanie, ponieważ dzieli tablicę równomiernie wielkości tablic, podczas gdy inne rozwiązania nie (we wszystkich innych rozwiązaniach, na które patrzyłem, ostatnia tablica może być dowolnie mała). - MiniQuark
Chciałbym znaleźć to jak 5 lat temu. Dzięki @Moj bardzo przydatna funkcja. - O.rka
@MiniQuark, ale co to znaczy, gdy liczba bloków nie jest czynnikiem oryginalnego rozmiaru tablicy? - Baldrickk


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_longestrozwią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



Cudownie, twoja prosta wersja jest moją ulubioną. Inni też wymyślili podstawowe islice(it, size) wyrażenie i osadzone (tak jak ja zrobiłem) w konstrukcji pętli. Tylko Ty pomyślałeś o dwuargumentowej wersji iter() (Byłem całkowicie nieświadomy), co czyni go super-eleganckim (i prawdopodobnie najbardziej wydajnym). Nie miałem pojęcia, że ​​pierwszy argument iter zmienia się na funkcję 0-argumentową, gdy podano wartownik. Zwracasz (nieskończony) iterator fragmentów, możesz użyć iteratora (nieskończonego) jako wejścia, nie ma len() i bez plasterków tablicy. Niesamowite! - ThomasH
Właśnie dlatego czytałem odpowiedzi, zamiast skanować tylko najlepsze pary. W moim przypadku wymagane było opcjonalne wypełnienie, a ja także dowiedziałem się o dwuargumentowej formie iteracji. - Kerr


def chunk(input, size):
    return map(None, *([iter(input)] * size))

47
2018-06-26 19:10



map(None, iter) równa się izip_longest(iter). - Thomas Ahle
@TomaszWysocki Czy możesz wyjaśnić * przed tobą krotka iterator? Być może w tekście odpowiedzi, ale zauważyłem to * używane w ten sposób w Pythonie wcześniej. Dzięki! - theJollySin
@JollySin W tym kontekście nazywa się to operatorem splat. Jego użycie wyjaśniono tutaj - stackoverflow.com/questions/5917522/unzipping-and-the-operator. - rlms
Zamknij, ale ostatnia porcja ma Brak elementów do wypełnienia. To może być lub nie być wadą. Naprawdę fajny wzór.