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