Seite 1 von 1

Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 10:37
von Dav1d
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

Re: Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 11:02
von Sirius3
@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.

Re: Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 11:12
von Dav1d
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.

Re: Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 11:59
von 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.

Re: Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 12:14
von Dav1d
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.

Re: Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 12:19
von snafu
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.

Re: Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 12:44
von snafu
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()

Re: Iterable in Chunks groupen

Verfasst: Samstag 28. Mai 2016, 12:57
von 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.