Iterable in Chunks groupen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Ich brauche eine Iterable/Liste in chunks einer bestimmten Länge: `foo(3, [1,2,3,4,5,6,7,8,9,0]) -> [1,2,3], [4,5,6], [7,8,9], [0]`, den Code den ich bisher immer genutzt habe:

Code: Alles auswählen

def grouper_it(n, iterable):
    # http://stackoverflow.com/a/8998040/969534
    it = iter(iterable)
    while True:
        chunk_it = itertools.islice(it, n)
        try:
            first_el = next(chunk_it)
        except StopIteration:
            return
        yield itertools.chain((first_el,), chunk_it)

Das funktioniert auch super, wenn man die Chunks nacheinander 'consumed', aber nicht in dem Szenarion:

Code: Alles auswählen

for chunk in grouper_it(3, [1,2,3,4,5,6,7,8,9,0]):
    print chunk, len(list(chunk))
# <itertools.chain object at 0x7f89dacdc850> 3
# <itertools.chain object at 0x7f89dacdc910> 3
# <itertools.chain object at 0x7f89dacdc850> 3
# <itertools.chain object at 0x7f89dacdc910> 1

print len(list(grouper_it(3, [1,2,3,4,5,6,7,8,9,0])))
# 10 -> sollte 4 sein
Ich verstehe warum das passiert (man sieht das recht schnell wenn man sich die Dokumentation zu itertools.islice anschaut), aber ich weiß nicht wie ich das effizient lösen kann.

Normalerweise ist das auch kein Problem, aber ich versuche über `gevent.Pool.pool.imap_unordered` eine Funktion mit Chunks einer bestimmten größe zu callen: `return pool.imap_unordered(foo, grouper_it(3, [1,2,3,4,5,6,7,8,9,0]))` und meine Funktion `foo` wird 10mal aufgerufen mit jeweils einem Eintrag.

Eine offensichtliche Lösung ist nicht iteratoren zu verwenden (bzw. `chunk_it = list(chunk_it)` in der Funktion zu machen), das ist leider keine Lösung weil meine Ausgangsliste durchaus mehr als 10 Elemente (mehr so an die 50k) hat.

PS: Der Codebox gefallen Links in Kommentaren anscheinend nicht
the more they change the more they stay the same
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Dav1d: entweder Du speicherst die Ergebnisse in einer Liste oder Du arbeitest sie in der Reihenfolge ab, wie sie ankommen. Ist doch irgendwie logisch? 50.000 Einträge ist ja auch noch eher eine kleine Liste :D.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Sirius3 hat geschrieben:@Dav1d: entweder Du speicherst die Ergebnisse in einer Liste oder Du arbeitest sie in der Reihenfolge ab, wie sie ankommen. Ist doch irgendwie logisch? 50.000 Einträge ist ja auch noch eher eine kleine Liste :D.
Welche Ergebnisse, die des groupers? Wie gesagt, ich weiß warum das so ist, ich hätte nur gerne eine Lösung die es mir erlaubt Chunk-weise parallel (gevent/imap) die Liste/Iterable abzuarbeiten.
Zu den 50k Einträgen: ich plane halt gerne vorraus, es sind atm. 50k Einträge zu denen alle 2 Tage ca. nochmal 50k hinzukommen, d.h. in einer woche sind das 400k, noch eine woche drauf 750k, in einem Monat 1.5M. Und das ist atm nur die Testversion um zu schauen was möglich ist.
the more they change the more they stay the same
BlackJack

@Dav1d: Du brauchst eine „lazy“-Stufe weniger. Die Elemente die der ”Grouper” liefert dürfen selbst keine Iterables sein. Du hast da ja SO verlinkt. Schau Dir da einfach mal die akzeptierte Antwort an, und nicht die modifizierte Version die Du aus einer anderen Antwort hast.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

BlackJack hat geschrieben:@Dav1d: Du brauchst eine „lazy“-Stufe weniger. Die Elemente die der ”Grouper” liefert dürfen selbst keine Iterables sein. Du hast da ja SO verlinkt. Schau Dir da einfach mal die akzeptierte Antwort an, und nicht die modifizierte Version die Du aus einer anderen Antwort hast.
Ich weiß:
Dav1d hat geschrieben:Eine offensichtliche Lösung ist nicht iteratoren zu verwenden (bzw. `chunk_it = list(chunk_it)` in der Funktion zu machen)
Ich frag mich nur ob es effizienter geht. Z.B. brauch ich die Elemente in den Chunks nicht sortiert, d.h. es müsste doch i-wie gehen das lazy zu haben und immer nur die nächsten N Elemente zu bekommen, die gerade da sind.
the more they change the more they stay the same
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Man kann bei einem Iterator prinzipiell nicht sagen, wieviele Elemente enthalten sind. Um dies zu ermitteln, muss man die Elemente aus dem Iterator herausziehen. Die Verwendung von `enumerate()` wäre eine Möglichkeit, dies on-the-fly – d.h. hier: ohne Listen – zu erledigen.

Hier ein Beispiel:

Code: Alles auswählen

def count_chunks(items, chunk_size):
    return sum(1 for i, item in enumerate(items) if i % chunk_size == 0)
EDIT: ``range(len(items))`` geht hier natürlich auch, sofern man die einzelnen Elemente nicht verwenden will.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal mit Gruppenzahl und Listen. Die Anzahl der zurückgelieferten Gruppen ist die Zahl aus der letzten Gruppe.

Code: Alles auswählen

from itertools import chain, islice

def make_chunks(items, chunk_size, chunk_type=list):
    iterator = iter(items)
    for i, item in enumerate(iterator, 1):
        chunk = chain([item], islice(iterator, chunk_size - 1))
        yield i, chunk_type(chunk)

def main():
    items = [1,2,3,4,5,6,7,8,9,0]
    for num_chunks, chunk in make_chunks(items, 3):
        print(chunk)
    print(num_chunks)

if __name__ == '__main__':
    main()
BlackJack

@Dav1d: Was ist denn daran ineffizient eine Liste aus einem Chunk zu machen in dem Augenblick wo man den Chunk als Liste benötigt? Du musst das ja nicht früher machen. Und was heisst Du brauchst die Elemente nicht in der Reihenfolge? In der Reihenfolge inerhalb eines Chunks oder ist die Reihenfolge generell egal? Dann könntest Du auch einfach ein `islice()` pro Chunk liefern. Allerdings gibt es dann bei den letzten Chunks keine Möglichkeit sicherzustellen das nur einer keine n Elemente hat. Dazu müsste man ja wissen wieviele noch da sind, was bei einem iterable allgemein nicht ermittelt werden kann ohne es zu ”verbrauchen”, und wenn man das machen muss, kann man auch gleich eine Liste für den Chunk erstellen.
Antworten