3115 Stimmen

Wie kann ich eine Liste in gleich große Stücke aufteilen?

Wie kann ich eine Liste beliebiger Länge in gleich große Stücke aufteilen?


Siehe auch: <a href="https://stackoverflow.com/q/434287">Wie man über eine Liste in Stücken iteriert</a>.<br>Um Zeichenfolgen zu segmentieren, siehe <a href="https://stackoverflow.com/questions/9475241">Zeichenfolge alle n Zeichen aufteilen?</a>.

40 Stimmen

Bevor Sie eine neue Antwort veröffentlichen, beachten Sie bitte, dass es bereits mehr als 60 Antworten auf diese Frage gibt. Stellen Sie sicher, dass Ihre Antwort Informationen enthält, die nicht unter den vorhandenen Antworten sind.

3 Stimmen

Die Zeichenfolgenäquivalente dieser Frage: String alle n Zeichen teilen? (während einige Antworten sich überschneiden und für beide gelten, gibt es einige, die eindeutig sind).

130voto

pylang Punkte 33775

Nicht das Rad neu erfinden.

UPDATE: Eine vollständige Lösung wurde in Python 3.12+ gefunden itertools.batched.

Gegeben

import itertools as it
import collections as ct

import more_itertools as mit

iterable = range(11)
n = 3

Code

itertools.batched++

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

Details

Die folgenden nicht nativen Ansätze wurden vor Python 3.12 vorgeschlagen:

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

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

(oder DIY, wenn Sie möchten)

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

Referenzen

<sup>+</sup> Eine Drittanbieter-Bibliothek, die <a href="https://docs.python.org/3/library/itertools.html#itertools-recipes" rel="noreferrer">itertools-Rezepte</a> und mehr implementiert. <code>> pip install more_itertools</code>

<sup>++</sup>Enthalten in Python Standardbibliothek 3.12+. <code>batched</code> ist ähnlich wie <code>more_itertools.chunked</code>.

1 Stimmen

Ich würde alle meine Punkte abgeben, um diese als erste Antwort zu machen

125voto

Markus Jarderot Punkte 83090

Hier ist ein Generator, der auf beliebigen Iterierbaren funktioniert:

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

91voto

lebenf Punkte 908

Einfach und doch elegant

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

Oder wenn Sie bevorzugen:

def chunks(L, n): return [L[x: x+n] for x in xrange(0, len(L), n)]
chunks(L, 10)

29 Stimmen

Du sollst keine Variable nach einer arabischen Zahl benennen. In einigen Schriftarten sind 1 und l nicht zu unterscheiden. Genauso wie 0 und O. Manchmal sogar I und 1.

25 Stimmen

@Alfe Defekte Schriftarten. Die Leute sollten solche Schriftarten nicht verwenden. Nicht für Programmierung, nicht für irgendetwas.

21 Stimmen

Lambdas sollten als anonyme Funktionen verwendet werden. Es macht keinen Sinn, sie auf diese Weise zu nutzen. Zudem gestaltet es das Debuggen schwieriger, da die Traceback-Meldung "in " statt "in chunks" berichten wird im Falle eines Fehlers. Viel Glück beim Finden eines Problems, wenn Sie eine ganze Menge davon haben :)

70voto

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

"Gleichmäßig große Stücke" bedeutet für mich, dass sie alle gleich lang sind, oder, falls das nicht der Fall ist, dass sie gleich lang sind. geringe Varianz in der Länge. Z.B. könnten 5 Körbe für 21 Artikel die folgenden Ergebnisse liefern:

>>> import statistics
>>> statistics.variance([5,5,5,5,1]) 
3.2
>>> statistics.variance([5,4,4,4,4]) 
0.19999999999999998

Ein praktischer Grund, das letztere Ergebnis zu bevorzugen: Wenn Sie diese Funktionen verwenden, um die Arbeit zu verteilen, haben Sie die Aussicht eingebaut, dass eine von ihnen wahrscheinlich weit vor den anderen fertig wird, so dass sie herumsitzen und nichts tun würde, während die anderen weiter hart arbeiten.

Kritik an anderen Antworten hier

Als ich diese Antwort ursprünglich schrieb, hatte keine der anderen Antworten gleichmäßig große Stücke - sie lassen alle ein Stück am Ende übrig, sind also nicht ausgewogen und haben eine größere Längenvarianz als nötig.

Zum Beispiel endet die aktuelle Top-Antwort mit:

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

Andere, wie list(grouper(3, range(7))) y chunk(range(7), 3) kehren beide zurück: [(0, 1, 2), (3, 4, 5), (6, None, None)] . El None sind nur Füllmaterial und meiner Meinung nach ziemlich unelegant. Sie sind NICHT gleichmäßig chunking die iterables.

Warum können wir diese nicht besser aufteilen?

Zyklus-Lösung

Eine ausgewogene Lösung auf hohem Niveau mit itertools.cycle Das ist die Art und Weise, wie ich es heute tun könnte. Hier ist der Aufbau:

from itertools import cycle
items = range(10, 75)
number_of_baskets = 10

Jetzt brauchen wir unsere Listen, in die wir die Elemente einfügen können:

baskets = [[] for _ in range(number_of_baskets)]

Schließlich schließen wir die Elemente, die wir zuweisen wollen, mit einem Zyklus der Körbe zusammen, bis wir keine Elemente mehr haben, was semantisch genau das ist, was wir wollen:

for element, basket in zip(items, cycle(baskets)):
    basket.append(element)

Hier ist das Ergebnis:

>>> from pprint import pprint
>>> pprint(baskets)
[[10, 20, 30, 40, 50, 60, 70],
 [11, 21, 31, 41, 51, 61, 71],
 [12, 22, 32, 42, 52, 62, 72],
 [13, 23, 33, 43, 53, 63, 73],
 [14, 24, 34, 44, 54, 64, 74],
 [15, 25, 35, 45, 55, 65],
 [16, 26, 36, 46, 56, 66],
 [17, 27, 37, 47, 57, 67],
 [18, 28, 38, 48, 58, 68],
 [19, 29, 39, 49, 59, 69]]

Um diese Lösung zu produzieren, schreiben wir eine Funktion und stellen die Typ-Annotationen bereit:

from itertools import cycle
from typing import List, Any

def cycle_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
    baskets = [[] for _ in range(min(maxbaskets, len(items)))]
    for item, basket in zip(items, cycle(baskets)):
        basket.append(item)
    return baskets

Im obigen Beispiel nehmen wir unsere Artikelliste und die maximale Anzahl von Körben. Wir erstellen eine Liste mit leeren Listen, an die wir jedes Element im Round-Robin-Verfahren anhängen.

Schnitte

Eine andere elegante Lösung ist die Verwendung von Slices - insbesondere der weniger häufig verwendeten Schritt Argument zu Slices. d.h.:

start = 0
stop = None
step = number_of_baskets

first_basket = items[start:stop:step]

Dies ist besonders elegant, da es den Slices egal ist, wie lang die Daten sind - das Ergebnis, unser erster Korb, ist nur so lang wie nötig. Wir müssen nur den Startpunkt für jeden Korb erhöhen.

Eigentlich könnte dies ein Einzeiler sein, aber aus Gründen der Lesbarkeit und um überlange Codezeilen zu vermeiden, werden wir mehrzeilig vorgehen:

from typing import List, Any

def slice_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
    n_baskets = min(maxbaskets, len(items))
    return [items[i::n_baskets] for i in range(n_baskets)]

Und islice aus dem Modul itertools bietet einen trägen Iterationsansatz, wie er ursprünglich in der Frage gefordert wurde.

Ich erwarte nicht, dass die meisten Anwendungsfälle davon profitieren, da die ursprünglichen Daten bereits vollständig in einer Liste materialisiert sind, aber bei großen Datensätzen könnte dies fast die Hälfte des Speicherbedarfs einsparen.

from itertools import islice
from typing import List, Any, Generator

def yield_islice_baskets(items: List[Any], maxbaskets: int) -> Generator[List[Any], None, None]:
    n_baskets = min(maxbaskets, len(items))
    for i in range(n_baskets):
        yield islice(items, i, None, n_baskets)

Ergebnisse anzeigen mit:

from pprint import pprint

items = list(range(10, 75))
pprint(cycle_baskets(items, 10))
pprint(slice_baskets(items, 10))
pprint([list(s) for s in yield_islice_baskets(items, 10)])

Aktualisierte frühere Lösungen

Hier ist eine weitere ausgewogene Lösung, die von einer Funktion abgeleitet ist, die ich in der Vergangenheit in der Produktion verwendet habe und die den Modulo-Operator verwendet:

def baskets_from(items, maxbaskets=25):
    baskets = [[] for _ in range(maxbaskets)]
    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 man ihn in eine Liste einfügt:

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 range(baskets):
        yield [items[y_i] for y_i in range(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 range(baskets):
        length = ceiling if x_i < stepdown else floor
        yield [items.next() for _ in range(length)]

Ausgabe

Um sie auszuprobieren:

print(baskets_from(range(6), 8))
print(list(iter_baskets_from(range(6), 8)))
print(list(iter_baskets_contiguous(range(6), 8)))
print(baskets_from(range(22), 8))
print(list(iter_baskets_from(range(22), 8)))
print(list(iter_baskets_contiguous(range(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(range(26), 5))
print(list(iter_baskets_from(range(26), 5)))
print(list(iter_baskets_contiguous(range(26), 5)))

Das wird ausgedruckt:

[[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 Stücke in denselben Längenmustern bereitstellt wie die beiden anderen, aber die Elemente sind alle in der richtigen Reihenfolge, und sie sind so gleichmäßig aufgeteilt, wie man eine Liste diskreter Elemente aufteilen kann.

0 Stimmen

Du sagst, dass keines der oben genannten gleich große Stücke liefert. Aber dieses hier tut es, genauso wie dieses hier.

2 Stimmen

@senderle, Der erste, list(grouper(3, xrange(7))), und der zweite, chunk(xrange(7), 3), geben beide zurück: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Die None's dienen nur als Füllung und sind meiner Meinung nach ziemlich unelegant. Sie teilen die Iterierbaren NICHT gleichmäßig auf. Danke für deine Stimme!

8 Stimmen

Sie stellen die Frage (ohne es explizit zu tun, deshalb tue ich das jetzt hier), ob gleich große Chunks (außer dem letzten, falls nicht möglich) oder ob ein ausgewogenes (so gut wie mögliches) Ergebnis häufiger benötigt wird. Sie nehmen an, dass die ausgewogene Lösung bevorzugt werden sollte; das mag wahr sein, wenn das, was Sie programmieren, der realen Welt nahe ist (z. B. ein Kartenmischalgorithmus für ein simuliertes Kartenspiel). In anderen Fällen (wie dem Ausfüllen von Zeilen mit Wörtern) wird man eher darauf bedacht sein, die Zeilen so voll wie möglich zu halten. Daher kann ich wirklich keine der beiden Varianten bevorzugen; sie sind einfach für verschiedene Anwendungsfälle gedacht.

65voto

Tomasz Wysocki Punkte 10546
def chunk(input, size):
    return map(None, *([iter(input)] * size))

0 Stimmen

Funktioniert nicht in Python 3.8, gilt das für 2.x?

1 Stimmen

Für Python 3.x: return map(lambda *x: x, *([iter(input)] * size)). Es lässt jedoch das Ende der Liste aus, wenn sie nicht in gleich große Teile aufgeteilt werden kann.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X