Wie teilt man eine Liste in gleich große Teile auf?

2267

Ich habe eine Liste beliebiger Länge, und ich muss sie in gleich große Stücke aufteilen und sie bearbeiten. Es gibt einige offensichtliche Möglichkeiten, dies zu tun, z. B. einen Zähler und zwei Listen zu führen. Wenn die zweite Liste voll ist, fügen Sie sie der ersten Liste hinzu und leeren Sie die zweite Liste für die nächste Datenrunde. Dies ist jedoch möglicherweise extrem teuer.

Ich habe mich gefragt, ob jemand eine gute Lösung für Listen beliebiger Länge hat, z. B. mit Generatoren.

Ich suchte nach etwas Nützlichem itertools, konnte aber nichts offensichtlich Nützliches finden. Könnte es aber verpasst haben.

Verwandte Frage: Was ist die "pythonischste" Methode, um eine Liste in Blöcken zu durchlaufen?

jespern
quelle
1
Bevor Sie eine neue Antwort veröffentlichen, sollten Sie berücksichtigen, dass diese Frage bereits über 60 Antworten enthält. Stellen Sie sicher, dass Ihre Antwort Informationen enthält, die nicht zu den vorhandenen Antworten gehören.
Janniks
Für Benutzer, die einen willkürlich kleinen letzten
Block

Antworten:

3151

Hier ist ein Generator, der die gewünschten Chunks liefert:

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

Wenn Sie Python 2 verwenden, sollten Sie xrange()anstelle von 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]

Sie können auch einfach das Listenverständnis verwenden, anstatt eine Funktion zu schreiben. Es ist jedoch eine gute Idee, Operationen wie diese in benannten Funktionen zu kapseln, damit Ihr Code leichter zu verstehen ist. Python 3:

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

Python 2-Version:

[lst[i:i + n] for i in xrange(0, len(lst), n)]
Ned Batchelder
quelle
71
Was passiert, wenn wir die Länge der Liste nicht sagen können? Versuchen Sie dies auf itertools.repeat ([1, 2, 3]), zB
jespern
47
Das ist eine interessante Erweiterung der Frage, aber die ursprüngliche Frage, die eindeutig zum Bearbeiten einer Liste gestellt wurde.
Ned Batchelder
33
Diese Funktionen müssen in der verdammten Standardbibliothek sein
4.
6
@ Calimo: Was schlägst du vor? Ich gebe Ihnen eine Liste mit 47 Elementen. Wie möchten Sie es in "gleichmäßig große Stücke" aufteilen? Das OP akzeptierte die Antwort, so dass sie mit dem letzten unterschiedlich großen Block eindeutig einverstanden sind. Vielleicht ist der englische Ausdruck ungenau?
Ned Batchelder
8
Bitte nennen Sie Ihre Variablen nicht l, es sieht genauso aus wie 1 und ist verwirrend. Die Leute kopieren Ihren Code und denken, dass dies in Ordnung ist.
Yasen
555

Wenn Sie etwas ganz Einfaches wollen:

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

Verwenden Sie xrange()anstelle von range()im Fall von Python 2.x.

oremj
quelle
6
Oder (wenn wir verschiedene Darstellungen dieser bestimmten Funktion machen) Sie können eine Lambda-Funktion definieren über: Lambda x, y: [x [i: i + y] für i im Bereich (0, len (x), y) ]. Ich liebe diese Methode des Listenverständnisses!
JP
4
Nach der Rückkehr muss es [, nicht (
alwbtc
2
"Super einfach" bedeutet, keine Endlosschleifen debuggen zu müssen - ein großes Lob für die max().
Bob Stein
Es gibt nichts Einfaches an dieser Lösung
mit
1
@Nhoj_Gonk Ups, es ist keine Endlosschleife, aber Chunks (L, 0) würden einen ValueError ohne max () auslösen. Stattdessen verwandelt max () alles, was kleiner als 1 ist, in eine 1.
Bob Stein
295

Direkt aus der (alten) Python-Dokumentation (Rezepte für 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)

Die aktuelle Version, wie von JFSebastian vorgeschlagen:

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

Ich denke, Guidos Zeitmaschine funktioniert - hat funktioniert - wird funktionieren - wird funktioniert haben - hat wieder funktioniert.

Diese Lösungen funktionieren, weil [iter(iterable)]*n(oder das Äquivalent in der früheren Version) ein Iterator erstellt wird, der nin der Liste wiederholt wird . izip_longestführt dann effektiv ein Round-Robin von "jedem" Iterator durch; Da dies derselbe Iterator ist, wird er bei jedem solchen Aufruf erweitert, was dazu führt, dass jedes dieser Zip-Roundrobin ein Tupel von nElementen generiert .

tzot
quelle
@ninjagecko: list(grouper(3, range(10)))kehrt zurück [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]und alle Tupel haben die Länge 3. Bitte erläutern Sie Ihren Kommentar, da ich ihn nicht verstehen kann. Wie nennt man ein Ding und wie definieren Sie es als Vielfaches von 3, wenn Sie erwarten, dass Ihr Ding ein Vielfaches von 3 ist? Vielen Dank im Voraus.
Zot
14
Dies wurde verbessert, da es auf Generatoren (no len) funktioniert und das allgemein schnellere itertools-Modul verwendet.
Michael Dillon
88
Ein klassisches Beispiel für einen ausgefallenen itertoolsfunktionalen Ansatz, der im Vergleich zu einer einfachen und naiven Implementierung von reinem Python einen unlesbaren Schlamm
ergibt
15
@wim Da diese Antwort als Ausschnitt aus der Python-Dokumentation begann, würde ich vorschlagen, dass Sie ein Problem auf bugs.python.org öffnen .
Zot
1
@pedrosaurio wenn l==[1, 2, 3]dann f(*l)gleichbedeutend ist mit f(1, 2, 3). Siehe diese Frage und die offizielle Dokumentation .
Zot
225

Ich weiß, das ist ein bisschen alt, aber noch niemand erwähnt 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
quelle
12
Auf diese Weise können Sie die Gesamtzahl der Blöcke festlegen, nicht die Anzahl der Elemente pro Block.
FizxMike
6
Sie können die Mathematik selbst machen. Wenn Sie 10 Elemente haben, können Sie sie in 2, 5 Element-Chunks oder fünf 2-Elemente-Chunks gruppieren
Moj
24
+1 Dies ist meine Lieblingslösung, da das Array in gleichmäßig große Arrays aufgeteilt wird, während andere Lösungen dies nicht tun (bei allen anderen Lösungen, die ich mir angesehen habe, kann das letzte Array beliebig klein sein).
MiniQuark
@MiniQuark, aber was macht das, wenn die Anzahl der Blöcke nicht von der ursprünglichen Arraygröße abhängt?
Baldrickk
1
@Baldrickk Wenn Sie N Elemente in K Blöcke aufteilen, haben die ersten N% K Blöcke N // K + 1 Elemente und der Rest N // K Elemente. Wenn Sie beispielsweise ein Array mit 108 Elementen in 5 Blöcke aufteilen, enthalten die ersten 108% 5 = 3 Blöcke 108 // 5 + 1 = 22 Elemente, und der Rest der Blöcke enthält 108 // 5 = 21 Elemente.
MiniQuark
147

Ich bin überrascht , niemand hat daran gedacht, mit iter‚s mit zwei Argumenten :

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

Dies funktioniert mit jedem iterierbaren Element und erzeugt eine träge Ausgabe. Es gibt eher Tupel als Iteratoren zurück, aber ich denke, es hat trotzdem eine gewisse Eleganz. Es füllt auch nicht auf; Wenn Sie Polsterung wünschen, reicht eine einfache Variation der oben genannten aus:

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

Wie bei den izip_longestbasierten Lösungen werden auch bei den oben genannten Pads immer Pads verwendet. Soweit ich weiß, gibt es kein ein- oder zweizeiliges itertools-Rezept für eine Funktion, die optional aufgefüllt werden kann. Durch die Kombination der beiden oben genannten Ansätze kommt dieser ziemlich nahe:

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

Ich glaube, dies ist der kürzeste vorgeschlagene Chunker, der eine optionale Polsterung bietet.

Wie Tomasz Gandor feststellte , werden die beiden Padding-Chunker unerwartet anhalten, wenn sie auf eine lange Folge von Pad-Werten stoßen. Hier ist eine letzte Variante, die dieses Problem auf vernünftige Weise umgeht:

_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)]
senderle
quelle
7
Wunderbar, deine einfache Version ist mein Favorit. Auch andere haben sich den Grundausdruck islice(it, size)ausgedacht und ihn (wie ich es getan hatte) in ein Schleifenkonstrukt eingebettet. Nur Sie haben an die Version mit zwei Argumenten von gedacht (von der iter()ich nichts wusste), die sie super elegant (und wahrscheinlich am leistungswirksamsten) macht. Ich hatte keine Ahnung, dass sich das erste Argument in itereine 0-Argument-Funktion ändert, wenn der Sentinel angegeben wird. Sie geben einen (pot. Infinite) Iterator von Chunks zurück, können einen (pot. Infinite) Iterator als Eingabe verwenden, haben keine len()und keine Array-Slices. Genial!
ThomasH
1
Aus diesem Grund habe ich die Antworten durchgelesen, anstatt nur das oberste Paar zu scannen. In meinem Fall war optionales Auffüllen erforderlich, und auch ich lernte die Zwei-Argument-Form von iter kennen.
Kerr
Ich habe das positiv bewertet, aber trotzdem - lasst es uns nicht übertreiben! Erstens kann Lambda schlecht sein (langsames Schließen über itIterator. Zweitens und am wichtigsten - Sie werden vorzeitig enden, wenn ein Teil von padvaltatsächlich in Ihrem Iterator vorhanden ist und verarbeitet werden sollte.
Tomasz Gandor
@TomaszGandor, ich nehme deinen ersten Punkt! Obwohl ich verstehe, dass Lambda nicht langsamer als eine gewöhnliche Funktion ist, haben Sie natürlich Recht, dass der Funktionsaufruf und die Suche nach Schließungen dies verlangsamen. Ich weiß zum Beispiel nicht, wie hoch der relative Leistungseinbruch im Vergleich zum izip_longestAnsatz sein würde - ich vermute, dass dies ein komplexer Kompromiss sein könnte. Aber ... wird das padvalProblem nicht von jeder Antwort hier geteilt, die einen padvalParameter bietet ?
senderle
1
@TomaszGandor, fair genug! Es war jedoch nicht allzu schwierig, eine Version zu erstellen, die dies behebt. (Beachten Sie auch, dass die erste Version, die verwendet ()als Sentinel, tut richtig Arbeit Dies liegt daran. tuple(islice(it, size))Ausbeuten , ()wenn itleer.)
senderle
93

Hier ist ein Generator, der mit beliebigen Iterablen arbeitet:

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

Beispiel:

>>> 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
quelle
52
def chunk(input, size):
    return map(None, *([iter(input)] * size))
Tomasz Wysocki
quelle
map(None, iter)gleich izip_longest(iter).
Thomas Ahle
1
@TomaszWysocki Kannst du das *Iterator-Tupel vor dir erklären ? Möglicherweise in Ihrem Antworttext, aber ich habe gesehen, *dass dies in Python zuvor so verwendet wurde. Vielen Dank!
TheJollySin
1
@theJollySin In diesem Zusammenhang wird es als Splat-Operator bezeichnet. Die Verwendung wird hier erläutert - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms
2
Schließen, aber der letzte Block enthält keine Elemente, um ihn auszufüllen. Dies kann ein Defekt sein oder nicht. Wirklich cooles Muster.
49

Einfach und doch elegant

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

oder wenn Sie es vorziehen:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
lebenf
quelle
18
Du sollst keine Variable in der Gestalt einer arabischen Zahl überspielen. In einigen Schriftarten 1und lsind nicht zu unterscheiden. Wie sind 0und O. Und manchmal sogar Iund 1.
Alfe
14
@ Alfe Defekte Schriftarten. Leute sollten solche Schriften nicht verwenden. Nicht zum Programmieren, nicht für irgendetwas .
Jerry B
17
Lambdas sollen als unbenannte Funktionen verwendet werden. Es macht keinen Sinn, sie so zu benutzen. Außerdem wird das Debuggen schwieriger, da der Traceback im Fehlerfall "in <lambda>" anstelle von "in Chunks" meldet. Ich wünsche Ihnen viel Glück beim Finden eines Problems, wenn Sie eine ganze Reihe von diesen haben :)
Chris Koston
1
es sollte 0 sein und nicht 1 innerhalb von xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta
HINWEIS: Für Python 3 verwenden Benutzer range.
Christian Dean
40

Kritik anderer Antworten hier:

Keine dieser Antworten ist gleich groß, sie alle hinterlassen am Ende einen kleinen Teil, sodass sie nicht vollständig ausbalanciert sind. Wenn Sie diese Funktionen zum Verteilen von Arbeit verwenden, haben Sie die Aussicht eingebaut, dass einer wahrscheinlich weit vor den anderen fertig wird, sodass er herumsteht und nichts tut, während die anderen weiter hart arbeiten.

Zum Beispiel endet die aktuelle Top-Antwort mit:

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

Ich hasse diesen Zwerg am Ende einfach!

Andere mögen list(grouper(3, xrange(7)))und chunk(xrange(7), 3)beide kehren zurück : [(0, 1, 2), (3, 4, 5), (6, None, None)]. Die Nonesind nur Polsterung und meiner Meinung nach eher unelegant. Sie teilen die Iterables NICHT gleichmäßig auf.

Warum können wir diese nicht besser teilen?

Meine Lösung (en)

Hier ist eine ausgewogene Lösung, angepasst von einer Funktion , die ich in der Produktion verwendet habe (Hinweis in Python 3 zu ersetzen xrangemit 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) 

Und ich habe einen Generator erstellt, der dasselbe tut, wenn Sie ihn in eine Liste aufnehmen:

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

Und schließlich, da ich sehe, dass alle oben genannten Funktionen Elemente in einer zusammenhängenden Reihenfolge zurückgeben (wie sie angegeben wurden):

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

Ausgabe

Um sie zu testen:

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

Welches druckt aus:

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

Beachten Sie, dass der zusammenhängende Generator Blöcke in den gleichen Längenmustern wie die beiden anderen bereitstellt, die Elemente jedoch alle in Ordnung sind und so gleichmäßig aufgeteilt sind, wie man eine Liste diskreter Elemente teilen kann.

Aaron Hall
quelle
Sie sagen, dass keiner der oben genannten Punkte gleichmäßig große Stücke liefert. Aber dieser tut es, genauso wie dieser .
Absender
1
@senderle, Der erste list(grouper(3, xrange(7)))und der zweite geben chunk(xrange(7), 3)beide zurück : [(0, 1, 2), (3, 4, 5), (6, None, None)]. Die Nonesind nur Polsterung und meiner Meinung nach eher unelegant. Sie teilen die Iterables NICHT gleichmäßig auf. Danke für Ihre Stimme!
Aaron Hall
4
Sie werfen die Frage auf (ohne es explizit zu tun, also mache ich das jetzt hier), ob gleich große Stücke (außer dem letzten, wenn nicht möglich) oder ob ein ausgewogenes (so gutes wie möglich) Ergebnis häufiger benötigt wird. Sie gehen davon aus, dass die ausgewogene Lösung zu bevorzugen ist; Dies kann zutreffen, wenn das, was Sie programmieren, der realen Welt nahe kommt (z. B. ein Kartenalgorithmus für ein simuliertes Kartenspiel). In anderen Fällen (wie beim Füllen von Zeilen mit Wörtern) möchte man die Zeilen lieber so voll wie möglich halten. Also kann ich nicht wirklich eins dem anderen vorziehen; Sie sind nur für verschiedene Anwendungsfälle.
Alfe
@ ChristopherBarrington-Leigh Guter Punkt, für DataFrames sollten Sie wahrscheinlich Slices verwenden, da ich glaube, dass DataFrame-Objekte normalerweise nicht beim Schneiden kopiert werden, z. B.import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall
1
@ AaronHall Ups. Ich habe meinen Kommentar gelöscht, weil ich meine Kritik hinterfragt habe, aber Sie waren schnell bei der Auslosung. Vielen Dank! In der Tat ist meine Behauptung, dass es für Datenrahmen nicht funktioniert, wahr. Wenn Elemente ein Datenrahmen sind, verwenden Sie einfach Ertragselemente [Bereich (x_i, Elementanzahl, Körbe)] als letzte Zeile. Ich habe eine separate (noch eine) Antwort angeboten, in der Sie die gewünschte (minimale) Gruppengröße angeben.
CPBL
38

Ich habe die beeindruckendste Python-Antwort in einem Duplikat dieser Frage gesehen:

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

Sie können n-Tupel für jedes n erstellen. Wenn ja a = range(1, 15), dann ist das Ergebnis:

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

Wenn die Liste gleichmäßig aufgeteilt wird, dann können Sie ersetzen zip_longestmit zip, da sonst die Triplett (13, 14, None)wäre verloren. Python 3 wird oben verwendet. Verwenden Sie für Python 2 izip_longest.

Noich
quelle
Das ist schön, wenn Ihre Liste und Ihre Blöcke kurz sind. Wie können Sie dies anpassen, um Ihre Liste in Blöcke von 1000 aufzuteilen? Sie werden keine Postleitzahl codieren (i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith
9
zip(i, i, i, ... i)Mit "chunk_size" können Argumente für zip () wie folgt geschrieben werden: zip(*[i]*chunk_size)Ob das eine gute Idee ist oder nicht, ist natürlich umstritten.
Wilson F
1
Der Nachteil dabei ist, dass Sie Elemente löschen, wenn Sie nicht gleichmäßig teilen, da die Zip-Datei am kürzesten iterierbar bleibt - & izip_longest würde Standardelemente hinzufügen.
Aaron Hall
zip_longestsollte verwendet werden, wie in: stackoverflow.com/a/434411/1959808
Ioannis Filippidis
Die Antwort mit range(1, 15)fehlt bereits Elemente, weil es 14 Elemente gibt range(1, 15), nicht 15.
Ioannis Filippidis
35

Wenn Sie die Listengröße kennen:

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

Wenn nicht (ein 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

Im letzteren Fall kann es schöner umformuliert werden, wenn Sie sicher sein können, dass die Sequenz immer eine ganze Anzahl von Blöcken einer bestimmten Größe enthält (dh es gibt keinen unvollständigen letzten Block).

atzz
quelle
Ich bin traurig, dass dies so weit unten begraben ist. Die IterChunks funktionieren für alles und sind die allgemeine Lösung und haben keine mir bekannten Einschränkungen.
Jason Dunkelberger
18

Die Toolz- Bibliothek hat folgende partitionFunktion:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
zach
quelle
Dies scheint der einfachste aller Vorschläge zu sein. Ich frage mich nur, ob es wirklich wahr sein kann, dass man eine Bibliothek eines Drittanbieters verwenden muss, um eine solche Partitionsfunktion zu erhalten. Ich hätte erwartet, dass etwas Äquivalentes mit dieser Partitionsfunktion als eingebaute Sprache existiert.
Kasperd
1
Sie können eine Partition mit itertools erstellen. aber ich mag die toolz bibliothek. Es ist eine von Clojure inspirierte Bibliothek für die Arbeit an Sammlungen in einem funktionalen Stil. Sie erhalten keine Unveränderlichkeit, aber Sie erhalten ein kleines Vokabular für die Arbeit an einfachen Sammlungen. Als Plus ist Cytoolz in Cython geschrieben und erhält einen schönen Leistungsschub. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
zach
Der Link aus Zachs Kommentar funktioniert, wenn Sie den abschließenden Schrägstrich weglassen
mit
17

Wenn Sie beispielsweise eine Blockgröße von 3 hätten, könnten Sie Folgendes tun:

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

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

Ich würde dies verwenden, wenn meine Blockgröße eine feste Zahl ist, die ich eingeben kann, z. B. '3', und würde mich nie ändern.

Ninjagecko
quelle
11
Dies funktioniert nicht, wenn len (iterable)% 3! = 0 ist. Die letzte (kurze) Gruppe von Zahlen wird nicht zurückgegeben.
Sherbang
16

Ich mag die von tzot und JFSebastian vorgeschlagene Version des Python-Dokuments sehr, aber sie hat zwei Mängel:

  • es ist nicht sehr explizit
  • Normalerweise möchte ich keinen Füllwert im letzten Block

Ich benutze dieses häufig in meinem Code:

from itertools import islice

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

UPDATE: Eine faule Chunks-Version:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))
Nikipore
quelle
Was ist die Unterbrechungsbedingung für die while TrueSchleife?
Wjandrea
@wjandrea: Das wird ausgelöst StopIteration, wenn das tupleleer ist und iterable.next()ausgeführt wird. Funktioniert jedoch nicht richtig in modernem Python, wo das Beenden eines Generators durchgeführt werden sollte return, nicht das Erhöhen StopIteration. EINtry/except StopIteration: return Rundum- Schleife (und der Wechsel iterable.next()zu next(iterable)für eine versionübergreifende Kompatibilität) behebt dies mit minimalem Overhead.
ShadowRanger
15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Wenn AA ein Array ist, ist SS die Blockgröße. Zum Beispiel:

>>> 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
quelle
2
es ist das beste und einfachste.
F.Tamy
2
kurz und einfach. Einfachheit über Komplexität.
dkrynicki
15

Ich war neugierig auf die Leistung verschiedener Ansätze und hier ist es:

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

Ergebnisse:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844
Alex T.
quelle
3
Benchmarking mit timeBibliothek ist keine gute Idee, wenn wir timeitModul haben
Azat Ibrakov
13

Code:

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)

Ergebnis:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Art. B.
quelle
12

Sie können die get_chunksFunktion der utilspieBibliothek auch wie folgt verwenden:

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

Sie können utilspieüber pip installieren :

sudo pip install utilspie

Haftungsausschluss: Ich bin der Schöpfer der Utilspie- Bibliothek .

Moinuddin Quadri
quelle
11

An diesem Punkt denke ich, dass wir einen rekursiven Generator brauchen , nur für den Fall ...

In Python 2:

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

In Python 3:

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

Im Falle einer massiven Alien-Invasion kann auch ein dekorierter rekursiver Generator nützlich sein:

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
mazieres
quelle
9

Mit Zuweisungsausdrücken in Python 3.8 wird es ganz nett:

import itertools

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

Dies funktioniert mit einer beliebigen iterierbaren Datei, nicht nur mit einer Liste.

>>> 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
quelle
1
Dies ist eine würdige neue Antwort auf diese Frage. Das gefällt mir eigentlich ganz gut. Ich bin skeptisch gegenüber Zuweisungsausdrücken, aber wenn sie funktionieren, funktionieren sie.
juanpa.arrivillaga
7

heh, einzeilige Version

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
quelle
36
Bitte verwenden Sie "def chunk" anstelle von "chunk = lambda". Es funktioniert genauso. Eine Linie. Gleiche Funktionen. VIEL einfacher zum n00bz zu lesen und zu verstehen.
S.Lott
4
@ S.Lott: nicht wenn die n00bz aus Schema kommen: P das ist kein echtes Problem. Es gibt sogar ein Keyword für Google! Welche anderen Funktionen zeigen wir, die wir aus Gründen des n00bz vermeiden? Ich denke, die Rendite ist nicht zwingend genug, um n00b-freundlich zu sein.
Janus Troelsen
16
Das aus def chunkstatt resultierende Funktionsobjekt chunk=lambdahat das Attribut'__ name__ 'chunk' anstelle von '<lambda>'. Der spezifische Name ist in Tracebacks nützlicher.
Terry Jan Reedy
1
@Alfe: Ich bin mir nicht sicher, ob man einen semantischen Hauptunterschied nennen könnte, aber ob es einen nützlichen Namen in einem Traceback gibt <lamba>oder nicht, ist zumindest ein bemerkenswerter Unterschied.
Martineau
1
Nachdem Sie einige davon auf Leistung getestet haben, ist DIESES großartig!
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

Verwendungszweck:

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

for seq in split_seq(seq, 3):
    print seq
Corey Goldberg
quelle
7

Eine weitere explizitere Version.

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
quelle
(12. September 2016) Diese Antwort ist die sprachunabhängigste und am einfachsten zu lesende.
D Adams
7

Ohne len () aufzurufen, was für große Listen gut ist:

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

Und das ist für iterables:

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

Der funktionelle Geschmack der oben genannten:

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

ODER:

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

ODER:

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
quelle
16
Es gibt keinen Grund, len()große Listen zu meiden . Es ist eine Operation mit konstanter Zeit.
Thomas Wouters
7

Hier ist eine Liste zusätzlicher Ansätze:

Gegeben

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Code

Die Standardbibliothek

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

Verweise

+ Eine Drittanbieter-Bibliothek, die itertools-Rezepte und mehr implementiert .> pip install more_itertools

Pylang
quelle
6

Siehe diese Referenz

>>> 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
quelle
3
Schön, aber am Ende werden Elemente gelöscht, wenn die Größe nicht mit einer ganzen Anzahl von Blöcken übereinstimmt, z. B. zip(*[iter(range(7))]*3)nur zurückgegeben [(0, 1, 2), (3, 4, 5)]und 6die Eingabe vergessen.
Alfe
6

Da reden alle hier über Iteratoren. boltonshat die perfekte Methode dafür, genannt iterutils.chunked_iter.

from boltons import iterutils

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

Ausgabe:

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

Aber wenn Sie nicht dem Gedächtnis gnädig sein wollen, können Sie Old-Way verwenden und das Ganze listin erster Linie mit speichern iterutils.chunked.

vishes_shell
quelle
Und dieser funktioniert tatsächlich unabhängig von der Reihenfolge, in der man sich die Subiteratoren ansieht !!
Peter Gerdes
6

Noch eine Lösung

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]
>>> 
Анатолий Панин
quelle
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
quelle
1
Obwohl dies möglicherweise nicht so kurz oder hübsch aussieht wie viele der auf itertools basierenden Antworten, funktioniert diese tatsächlich, wenn Sie die zweite Unterliste vor dem Zugriff auf die erste ausdrucken möchten, dh Sie können i0 = next (g2) setzen. i1 = next (g2); und benutze i1 bevor du i0 benutzt und es bricht nicht !!
Peter Gerdes
5

Erwägen Sie die Verwendung von matplotlib.cbook Teilen

zum Beispiel:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
schwater
quelle
Sieht so aus, als hätten Sie versehentlich zwei Konten erstellt. Sie können sich an das Team wenden , um sie zusammenzuführen, sodass Sie die direkten Bearbeitungsrechte für Ihre Beiträge wiedererlangen können.
Georgy