Jak podzielisz listę na kawałki o jednakowej wielkości?

2263

Mam listę o dowolnej długości i muszę ją podzielić na kawałki o równej wielkości i operować na niej. Istnieje kilka oczywistych sposobów, jak to zrobić, takich jak prowadzenie licznika i dwóch list, a gdy druga lista się zapełni, dodaj ją do pierwszej listy i opróżnij drugą listę do następnej rundy danych, ale jest to potencjalnie niezwykle kosztowne.

Zastanawiałem się, czy ktoś ma dobre rozwiązanie tego problemu dla list dowolnej długości, np. Używając generatorów.

Szukałem czegoś przydatnego, itertoolsale nie mogłem znaleźć niczego oczywiście przydatnego. Może jednak tego nie zauważyłem.

Powiązane pytanie: Jaki jest najbardziej „pytonowy” sposób na iterację listy w częściach?

jespern
źródło
1
Zanim opublikujesz nową odpowiedź, zastanów się, że istnieje już ponad 60 odpowiedzi na to pytanie. Upewnij się, że twoja odpowiedź zawiera informacje, których nie ma wśród odpowiedzi istniejących.
janniks
Dla użytkowników, którzy chcą uniknąć arbitralnie małej końcowej porcji, spójrz na Dzielenie listy na N części o mniej więcej równej długości
wim

Odpowiedzi:

3145

Oto generator, który daje pożądane fragmenty:

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[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 Python 2, powinieneś użyć xrange()zamiast range():

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in xrange(0, len(lst), n):
        yield lst[i:i + n]

Możesz także po prostu użyć funkcji listy zamiast pisać funkcję, chociaż dobrze jest zawrzeć takie operacje w nazwanych funkcjach, aby kod był łatwiejszy do zrozumienia. Python 3:

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

Wersja Python 2:

[lst[i:i + n] for i in xrange(0, len(lst), n)]
Ned Batchelder
źródło
71
Co się stanie, jeśli nie będziemy mogli określić długości listy? Spróbuj tego na itertools.repeat ([1, 2, 3]), np.
jespern
47
To ciekawe rozszerzenie pytania, ale pierwotne pytanie wyraźnie zadawało pytanie o działanie na liście.
Ned Batchelder
32
ta funkcja musi być w cholernej standardowej bibliotece
dgan
6
@ Calimo: co sugerujesz? Wręczam ci listę z 47 elementami. Jak chciałbyś podzielić go na „kawałki równej wielkości”? OP zaakceptował odpowiedź, więc są wyraźnie w porządku z ostatnim kawałkiem różnej wielkości. Być może angielskie zdanie jest nieprecyzyjne?
Ned Batchelder
8
Proszę nie nazywać zmiennych l, wygląda dokładnie jak 1 i jest mylący. Ludzie kopiują twój kod i myślą, że to jest w porządku.
Yasen
555

Jeśli chcesz czegoś bardzo prostego:

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

Użyj xrange()zamiast range()w przypadku Python 2.x

oremj
źródło
6
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!
JP
4
po powrocie musi być [, not (
alwbtc
2
„Super prosty” oznacza brak konieczności debugowania nieskończonych pętli - uznanie dla max().
Bob Stein
nie ma nic prostego w tym rozwiązaniu
mit
1
@Nhoj_Gonk Ups, to nie jest nieskończona pętla, ale fragmenty (L, 0) podniosłyby błąd ValueError bez max (). Zamiast tego, max () zamienia wszystko mniejsze niż 1 na 1.
Bob Stein
294

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, zgodnie z sugestią JFSebastian:

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

Wydaje mi się, że maszyna czasu Guido działa - działała - będzie działać - będzie działać - działała ponownie.

Te rozwiązania działają, ponieważ [iter(iterable)]*n(lub odpowiednik we wcześniejszej wersji) tworzy jeden iterator, powtarzany nraz na liście. izip_longestnastępnie efektywnie wykonuje okrężny „iterator”; ponieważ jest to ten sam iterator, jest on przyspieszany przy każdym takim wywołaniu, w wyniku czego każde takie okrążenie zip generuje jedną krotkę nprzedmiotów.

tzot
źródło
@ ninjagecko: list(grouper(3, range(10)))zwraca [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)], a wszystkie krotki mają długość 3. Proszę rozwinąć swój komentarz, ponieważ nie rozumiem tego; jak nazywasz rzecz i jak definiujesz, że jest to wielokrotność 3 w „spodziewaniu się, że twoja rzecz będzie wielokrotnością 3”? Z góry dziękuję.
tzot
14
przegłosował to, ponieważ działa na generatorach (bez Len) i używa ogólnie szybszego modułu itertools.
Michael Dillon
88
Klasyczny przykład fantazyjnego itertoolspodejścia funkcjonalnego, które okazuje się nieczytelnym szlamem, w porównaniu do prostej i naiwnej implementacji czystego pytona
wim 12'13
15
@ wim Biorąc pod uwagę, że ta odpowiedź zaczęła się od fragmentu z dokumentacji Pythona, sugeruję, aby otworzyć problem na stronie bugs.python.org .
tzot
1
@pedrosaurio, jeśli l==[1, 2, 3]wtedy f(*l)jest równoważne f(1, 2, 3). Zobacz to pytanie i oficjalną dokumentację .
tzot
224

Wiem, że to trochę stare, ale nikt jeszcze nie wspomniał 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])]
Moj
źródło
12
Pozwala to ustawić całkowitą liczbę porcji, a nie liczbę elementów na porcję.
FizxMike,
6
możesz sam zrobić matematykę. jeśli masz 10 elementów, możesz je zgrupować w 2, 5-częściowe fragmenty lub pięć 2-elementowych fragmentów
Moj
24
+1 To jest moje ulubione rozwiązanie, gdyż dzieli tablicę do równomiernie wielkości tablic, podczas gdy inne rozwiązania nie (we wszystkich innych rozwiązań Spojrzałem na ostatnia tablica może być dowolnie małe).
MiniQuark,
@MiniQuark, ale co to robi, gdy liczba bloków nie jest czynnikiem oryginalnego rozmiaru tablicy?
Baldrickk
1
@Baldrickk Jeśli podzielisz N elementów na K fragmentów, wówczas pierwsze N% K fragmentów będzie miało N // K + 1 elementów, a reszta będzie miała N // K elementów. Na przykład, jeśli podzielisz tablicę zawierającą 108 elementów na 5 części, wówczas pierwsze 108% 5 = 3 części będą zawierały 108 // 5 + 1 = 22 elementy, a reszta części będzie miała 108 // 5 = 21 elementy.
MiniQuark,
147

Jestem zaskoczony, nikt nie pomyślał o użyciu iter„s forma dwóch argumentów :

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żdym iterowalnym i produkuje leniwie. Zwraca krotki zamiast iteratorów, ale myślę, że ma jednak pewną elegancję. To również nie pad; jeśli chcesz uzupełnienia, wystarczy prosta odmiana powyższego:

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

Podobnie jak izip_longestrozwiązania oparte na powyższym zawsze się nakładają. O ile mi wiadomo, nie ma jedno- lub dwuwierszowego przepisu na itertools dla funkcji, która opcjonalnie działa na pad. Łącząc powyższe dwa podejścia, ten jest bardzo zbliżony:

_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ótszy zaproponowany fragment, który oferuje opcjonalne wypełnienie.

Jak zauważył Tomasz Gandor , dwa fragmenty paddingu niespodziewanie zatrzymają się, jeśli napotkają długą sekwencję wartości padu. Oto ostatnia odmiana, która w rozsądny sposób rozwiązuje ten problem:

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

Próbny:

>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]
senderle
źródło
7
Cudownie, twoja prosta wersja jest moją ulubioną. Inni również wymyślili podstawowe islice(it, size)wyrażenie i osadzili je (tak jak ja to zrobiłem) w konstrukcji pętli. Tylko ty pomyślałeś o dwuargumentowej wersji iter()(byłem całkowicie nieświadomy), co czyni ją super-elegancką (i prawdopodobnie najbardziej efektywną pod względem wydajności). Nie miałem pojęcia, że ​​pierwszy argument do iterzmiany na funkcję 0-argumentową po otrzymaniu wartownika. Zwracamy iterator porcji (pot. Nieskończony), można użyć iteratora (pot. Nieskończony) jako danych wejściowych, nie ma len()żadnych wycinków tablicy i nie ma ich. Niesamowite!
ThomasH
1
To dlatego czytam odpowiedzi, zamiast skanować tylko parę najlepszych. Opcjonalne wypełnienie było w moim przypadku wymogiem i ja również dowiedziałem się o dwuargumentowej formie iteracji.
Kerr,
Głosowałem za tym, ale nadal - nie przesadzajmy! Po pierwsze, lambda może być zła (powolne zamykanie nad ititeratorem. Po drugie i najważniejsze) - skończysz przedwcześnie, jeśli część padvalfaktycznie istnieje w twojej iteracji i powinna zostać przetworzona
Tomasz Gandor
@TomaszGandor, biorę twój pierwszy punkt! Chociaż rozumiem, że lambda nie jest wolniejsza niż zwykła funkcja, oczywiście masz rację, że wywołanie funkcji i przeglądanie zamknięcia spowolni to. Nie wiem na przykład, jaki byłby tego względny wpływ na wydajność w porównaniu z izip_longestpodejściem - podejrzewam, że może to być złożony kompromis. Ale ... czy padvalproblem nie jest wspólny dla każdej odpowiedzi tutaj, która oferuje padvalparametr?
senderle
1
@TomaszGandor, wystarczy! Ale nie było trudno stworzyć wersję, która to naprawia. (Należy również pamiętać, że sama pierwsza wersja, która wykorzystuje ()jako wartownik, czyni pracę prawidłowo Dzieje się tak dlatego, tuple(islice(it, size))plony (), gdy itjest pusta.)
senderle
93

Oto generator, który działa na dowolnych iteracjach:

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]]
Markus Jarderot
źródło
52
def chunk(input, size):
    return map(None, *([iter(input)] * size))
Tomasz Wysocki
źródło
map(None, iter)równa izip_longest(iter).
Thomas Ahle,
1
@TomaszWysocki Czy potrafisz wyjaśnić *przed sobą krotkę iteratora? Być może w tekście odpowiedzi, ale zauważyłem, *że wcześniej tak było w Pythonie. Dzięki!
theJollySin
1
@theJollySin W tym kontekście jest nazywany operatorem splat. Jego zastosowanie jest wyjaśnione tutaj - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms
2
Zamknij, ale ostatnia porcja nie zawiera żadnych elementów do wypełnienia. To może, ale nie musi być wada. Jednak naprawdę fajny wzór.
48

Prosty, ale elegancki

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

lub jeśli wolisz:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
lebenf
źródło
18
Nie będziesz kopiować zmiennej na podobieństwo liczby arabskiej. W niektórych czcionek, 1i lsą nie do odróżnienia. Tak jak 0i O. A czasem nawet Ii 1.
Alfe
14
@Alfe Wadliwe czcionki. Ludzie nie powinni używać takich czcionek. Nie do programowania, nie do niczego .
Jerry B,
17
Lambdas mają być używane jako funkcje bez nazw. Nie ma sensu ich tak używać. Ponadto utrudnia to debugowanie, ponieważ w przypadku błędu funkcja śledzenia wyświetli „w <lambda>” zamiast „w porcjach”. Życzę powodzenia w znalezieniu problemu, jeśli masz ich całą masę :)
Chris Koston
1
powinno być 0, a nie 1 wewnątrz xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta
UWAGA: W przypadku użytkowników Python 3 użyj range.
Christian Dean
40

Krytyka innych odpowiedzi tutaj:

Żadna z tych odpowiedzi nie jest kawałkami o równej wielkości, wszystkie pozostawiają fragment runtu na końcu, więc nie są całkowicie zrównoważone. Jeśli użyjesz tych funkcji do rozłożenia pracy, masz wbudowaną perspektywę, że jedna z nich prawdopodobnie zakończy się znacznie wcześniej niż inne, więc siedziałaby bezczynnie, podczas gdy inne nadal ciężko pracowały.

Na przykład bieżąca górna odpowiedź kończy się na:

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

Po prostu nienawidzę tego wyścigu!

Inni, jak list(grouper(3, xrange(7))), i chunk(xrange(7), 3)zarówno zwrot: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Są Noneto tylko wypełnienia i moim zdaniem raczej nieeleganckie. NIE są równomiernie dzielące iteracyjne.

Dlaczego nie możemy ich lepiej podzielić?

Moje rozwiązanie

Oto wyważone rozwiązanie, dostosowane z funkcji Użyłem w produkcji (Uwaga: W Pythonie 3 do zastąpienia xrangez 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 stworzyłem generator, który robi to samo, jeśli umieścisz go na liście:

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 wreszcie, ponieważ widzę, że wszystkie powyższe funkcje zwracają elementy w ciągłej kolejności (tak jak je podano):

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

Wynik

Aby je przetestować:

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

Który drukuje:

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

Zauważ, że ciągły generator zapewnia fragmenty o takich samych wzorcach długości jak pozostałe dwa, ale wszystkie elementy są w porządku i są one równomiernie podzielone, tak jak można podzielić listę odrębnych elementów.

Aaron Hall
źródło
Mówisz, że żadne z powyższych nie zapewnia kawałków o równej wielkości. Ale ten robi, podobnie jak ten .
nadawca
1
@senderle, pierwszy, list(grouper(3, xrange(7)))oraz drugi, chunk(xrange(7), 3)zwracają: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Są Noneto tylko wypełnienia i moim zdaniem raczej nieeleganckie. NIE są równomiernie dzielące iteracyjne. Dziękuję za Twój głos!
Aaron Hall
4
Zadajesz pytanie (nie robiąc tego wprost, więc robię to teraz), czy kawałki o jednakowej wielkości (z wyjątkiem ostatniego, jeśli nie jest to możliwe) lub czy zrównoważony (tak dobry, jak to możliwe) wynik jest częściej, co będzie potrzebne. Zakładasz, że zrównoważone rozwiązanie jest lepsze; może to być prawdą, jeśli to, co programujesz, jest zbliżone do realnego świata (np. algorytm rozdawania kart w symulowanej grze karcianej). W innych przypadkach (np. Wypełnianie wierszy słowami) wolimy, aby wiersze były jak najbardziej pełne. Więc nie mogę tak naprawdę preferować jednego od drugiego; są tylko dla różnych przypadków użycia.
Alfe
@ ChristopherBarrington-Leigh Dobra uwaga, jeśli chodzi o DataFrames, prawdopodobnie powinieneś użyć plasterków, ponieważ uważam, że obiekty DataFrame zwykle nie kopiują się podczas krojenia, np.import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall
1
@AaronHall Oops. Usunąłem swój komentarz, ponieważ po raz drugi odgadłem moją krytykę, ale szybko zwróciłeś uwagę na losowanie. Dzięki! W rzeczywistości moje twierdzenie, że nie działa w przypadku ramek danych, jest prawdziwe. Jeśli items jest ramką danych, po prostu użyj itemów dochodu [zakres (x_i, item_count, koszyki)] jako ostatniego wiersza. Podałem osobną (jeszcze inną) odpowiedź, w której określasz pożądaną (minimalną) wielkość grupy.
CPBL
38

Widziałem najbardziej niesamowitą odpowiedź w języku Python w duplikacie tego pytania:

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

Możesz utworzyć n-krotkę dla dowolnego n. Jeśli a = range(1, 15), to wynik będzie:

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

Jeśli lista jest podzielona równo, to można zastąpić zip_longestw zipprzeciwnym razie trójka (13, 14, None)zostaną utracone. Python 3 jest używany powyżej. W przypadku Python 2 użyj izip_longest.

Noich
źródło
to miłe, jeśli twoja lista i fragmenty są krótkie, jak możesz to dostosować, aby podzielić listę na fragmenty po 1000? nie zamierzasz kodować zip (i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith
9
zip(i, i, i, ... i)z argumentem „chunk_size” argumenty zip () można zapisać jako: zip(*[i]*chunk_size)Oczywiście, czy to dobry pomysł, czy nie.
Wilson F
1
Wadą tego jest to, że jeśli nie rozdzielisz równomiernie, upuścisz elementy, ponieważ zip zatrzymuje się w najkrótszej iteracji - & izip_longest dodałby elementy domyślne.
Aaron Hall
zip_longestnależy użyć, jak to zrobiono w: stackoverflow.com/a/434411/1959808
Ioannis Filippidis
W odpowiedzi range(1, 15)już brakuje elementów, ponieważ jest 14 elementów range(1, 15), a nie 15.
Ioannis Filippidis
35

Jeśli znasz rozmiar listy:

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

Jeśli nie (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

W tym drugim przypadku można go przepiękniej sformułować, jeśli masz pewność, że sekwencja zawsze zawiera całą liczbę fragmentów o danym rozmiarze (tj. Nie ma niekompletnej ostatniej części).

atzz
źródło
Przykro mi, że jest tak głęboko pochowany. IterChunks działa na wszystko i jest ogólnym rozwiązaniem i nie ma żadnych ostrzeżeń, o których wiem.
Jason Dunkelberger,
18

Toolz biblioteka posiada partitionfunkcję dla tego:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
zach
źródło
To wygląda na najprostszą ze wszystkich sugestii. Zastanawiam się tylko, czy naprawdę może być prawdą, że do uzyskania takiej funkcji partycjonowania trzeba użyć biblioteki innej firmy. Oczekiwałbym, że coś równoważnego z tą funkcją partycji będzie istniało jako wbudowany język.
kasperd
1
możesz zrobić partycję za pomocą itertools. ale lubię bibliotekę toolz. to inspirowana clojure biblioteka do pracy nad kolekcjami w funkcjonalnym stylu. nie dostajesz niezmienności, ale masz słownictwo do pracy nad prostymi kolekcjami. Jako plus, cytoolz jest napisany w cytonie i dostaje niezły wzrost wydajności. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
zach
Link z komentarza Zach działa, jeśli zwolnisz końcowy ukośnik: matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
mit
17

Jeśli na przykład masz porcję o wielkości 3, możesz:

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

źródło: http://code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/

Użyłbym tego, gdy mój rozmiar fragmentu ma ustaloną liczbę, którą mogę wpisać, np. „3”, i nigdy się nie zmieniam.

ninjagecko
źródło
11
To nie działa, jeśli len (iterable)% 3! = 0. Ostatnia (krótka) grupa liczb nie zostanie zwrócona.
sherbang
16

Bardzo podoba mi się wersja doc Pytona zaproponowana przez tzot i JFSebastian, ale ma dwie wady:

  • nie jest to bardzo jednoznaczne
  • Zwykle nie chcę wartości wypełnienia w ostatnim fragmencie

Często używam tego w moim kodzie:

from itertools import islice

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

AKTUALIZACJA: Leniwa wersja kawałków:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))
nikipore
źródło
Jaki jest warunek przerwania while Truepętli?
wjandrea
@wjandrea: StopIterationPodniesiony, gdy tuplejest pusty i iterable.next()zostanie wykonany. Nie działa jednak poprawnie we współczesnym Pythonie, gdzie należy wyjść z generatora return, a nie podnosićStopIteration . try/except StopIteration: returnWokół całej pętli (i zmienia iterable.next()się next(iterable)na cross-wersja COMPAT) rozwiązuje to przy minimalnym obciążeniu przynajmniej.
ShadowRanger
15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Gdzie AA jest tablicą, SS jest rozmiarem porcji. Na przykład:

>>> 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
Riaz Rizvi
źródło
2
to jest najlepsze i proste.
F.Tamy
2
krótki i prosty. prostota ponad złożoność.
dkrynicki
15

Byłem ciekawy wydajności różnych podejść i oto:

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

Wyniki:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844
Alex T.
źródło
3
porównywanie z wykorzystaniem timebiblioteki nie jest świetnym pomysłem, gdy mamy timeitmoduł
Azat Ibrakov
13

kod:

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)

wynik:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Art. B
źródło
12

Możesz także użyć get_chunksfunkcji utilspiebiblioteki jako:

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

Możesz zainstalować utilspieprzez pip:

sudo pip install utilspie

Oświadczenie: Jestem twórcą biblioteki utilspie .

Moinuddin Quadri
źródło
11

W tym momencie myślę, że potrzebujemy generatora rekurencyjnego , na wypadek gdyby ...

W python 2:

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

W python 3:

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

Ponadto, w przypadku masowej inwazji obcych, przydatny generator rekurencyjny może się przydać:

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
labirynty
źródło
9

Z wyrażeniach przypisania w Pythonie 3.8 staje się całkiem ładnie:

import itertools

def batch(iterable, size):
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item

Działa to na dowolnej iterowalnej, nie tylko na liście.

>>> import pprint
>>> pprint.pprint(list(batch(range(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]]
nirvana-msu
źródło
1
To jest godna nowa odpowiedź na to pytanie. Właściwie to mi się podoba. Jestem sceptyczny wobec wyrażeń przypisania, ale kiedy działają, działają.
juanpa.arrivillaga
7

heh, wersja jednoliniowa

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]]
slav0nic
źródło
36
Proszę użyć „def chunk” zamiast „chunk = lambda”. Działa tak samo. Jedna linia. Te same funkcje. O wiele łatwiej dla n00bz czytać i rozumieć.
S.Lott,
4
@ S.Lott: nie, jeśli n00bz pochodzi ze schematu: P to nie jest prawdziwy problem. jest nawet słowo kluczowe do google! jakie inne funkcje pokazują, że unikamy ze względu na n00bz? Myślę, że wydajność nie jest konieczna / c-podobna, aby być przyjaznym dla n00b.
Janus Troelsen
16
Obiekt funkcji wynikający z def chunkzamiast chunk=lambdama .__ name__ atrybut „chunk” zamiast „<lambda>”. Określona nazwa jest bardziej przydatna w trackbackach.
Terry Jan Reedy
1
@Alfe: Nie jestem pewien, czy można by to nazwać główną różnicą semantyczną, ale to, czy istnieje użyteczna nazwa w traceback zamiast <lamba>czy nie, jest przynajmniej zauważalną różnicą.
martineau,
1
Po przetestowaniu wielu z nich pod kątem wydajności, TO jest świetne!
Sunny Patel,
7
def split_seq(seq, num_pieces):
    start = 0
    for i in xrange(num_pieces):
        stop = start + len(seq[i::num_pieces])
        yield seq[start:stop]
        start = stop

stosowanie:

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for seq in split_seq(seq, 3):
    print seq
Corey Goldberg
źródło
7

Kolejna bardziej wyraźna wersja.

def chunkList(initialList, chunkSize):
    """
    This function chunks a list into sub lists 
    that have a length equals to chunkSize.

    Example:
    lst = [3, 4, 9, 7, 1, 1, 2, 3]
    print(chunkList(lst, 3)) 
    returns
    [[3, 4, 9], [7, 1, 1], [2, 3]]
    """
    finalList = []
    for i in range(0, len(initialList), chunkSize):
        finalList.append(initialList[i:i+chunkSize])
    return finalList
Ranaivo
źródło
(12 września 2016 r.) Ta odpowiedź jest najbardziej niezależna od języka i najłatwiejsza do odczytania.
D Adams,
7

Bez wywoływania len (), co jest dobre dla dużych list:

def splitter(l, n):
    i = 0
    chunk = l[:n]
    while chunk:
        yield chunk
        i += n
        chunk = l[i:i+n]

Dotyczy to iteracji:

def isplitter(l, n):
    l = iter(l)
    chunk = list(islice(l, n))
    while chunk:
        yield chunk
        chunk = list(islice(l, n))

Funkcjonalny smak powyższego:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(l))))

LUB:

def chunks_gen_sentinel(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return iter(imap(tuple, continuous_slices).next,())

LUB:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))
Mars
źródło
16
Nie ma powodu, aby unikać len()dużych list; jest to operacja o stałym czasie.
Thomas Wouters
7

Oto lista dodatkowych podejść:

Dany

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Kod

Biblioteka standardowa

list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

d = {}
for i, x in enumerate(iterable):
    d.setdefault(i//n, []).append(x)

list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
    dd[i//n].append(x)

list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

more_itertools+

list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]

list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

Bibliografia

+ Biblioteka innej firmy, która implementuje przepisy itertools i nie tylko.> pip install more_itertools

pylang
źródło
6

Zobacz to odniesienie

>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>> 

Python3

macm
źródło
3
Ładne, ale upuszcza elementy na końcu, jeśli rozmiar nie pasuje do pełnej liczby porcji, np. zip(*[iter(range(7))]*3)Zwraca tylko [(0, 1, 2), (3, 4, 5)]i zapomina 6o danych wejściowych.
Alfe
6

Ponieważ wszyscy tutaj mówią o iteratorach. boltonsma do tego idealną metodę o nazwie iterutils.chunked_iter.

from boltons import iterutils

list(iterutils.chunked_iter(list(range(50)), 11))

Wynik:

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

Ale jeśli nie chcesz litości dla pamięci, możesz użyć starej metody i przechowywać ją w całości listza pomocą iterutils.chunked.

vishes_shell
źródło
I to faktycznie działa bez względu na kolejność patrzy się na subiteratory !!
Peter Gerdes,
6

Jeszcze jedno rozwiązanie

def make_chunks(data, chunk_size): 
    while data:
        chunk, data = data[:chunk_size], data[chunk_size:]
        yield chunk

>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
...     print chunk
... 
[1, 2]
[3, 4]
[5, 6]
[7]
>>> 
Анатолий Панин
źródło
5
def chunks(iterable,n):
    """assumes n is an integer>0
    """
    iterable=iter(iterable)
    while True:
        result=[]
        for i in range(n):
            try:
                a=next(iterable)
            except StopIteration:
                break
            else:
                result.append(a)
        if result:
            yield result
        else:
            break

g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'
Robert King
źródło
1
Chociaż może to nie wyglądać tak krótko lub tak ładnie, jak wiele odpowiedzi opartych na itertoolach, ta faktycznie działa, jeśli chcesz wydrukować drugą podlistę przed uzyskaniem dostępu do pierwszej, tj. Możesz ustawić i0 = next (g2); i1 = następny (g2); i użyj i1 przed użyciem i0 i nie psuje się !!
Peter Gerdes,
5

Rozważ użycie kawałków matplotlib.cbook

na przykład:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
schwater
źródło
Wygląda na to, że przypadkowo utworzyłeś dwa konta. Możesz skontaktować się z zespołem, aby je scalić, co pozwoli ci odzyskać uprawnienia do bezpośredniej edycji swoich wpisów.
Georgy