iter steps ?!?!

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
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Das folgende muß doch einfacher gehen, oder?

Code: Alles auswählen

def iter_steps(g, steps):
    """
    >>> for v in iter_steps([1,2,3,4], steps=2): v
    [1, 2]
    [2, 3]
    [3, 4]
    >>> for v in iter_steps([1,2,3,4,5], steps=3): v
    [1, 2, 3]
    [2, 3, 4]
    [3, 4, 5]
    """
    values = []
    for value in g:
        values.append(value)
        if len(values)==steps:
            yield values
            values.pop(0)


if __name__ == "__main__":
    import doctest
    print doctest.testmod(
        verbose=False
        #~ verbose=True
    )

Also einmal die Funktion an für sich und einmal der DocTest dafür?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Ich würde das vielleicht eher `iter_window()` oder so nennen, denn das ist letztendlich was getan wird. Und ich würde nicht `values` rausgeben. Wenn das jemand von aussen verändert, dann funktioniert das nicht mehr wie es soll.

Wenn das für beliebige iterierbare Objekte funktionieren soll, dann ist das schon ganz gut IMHO. Wobei ich eine `collections.deque` verwenden würde, statt der Liste.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

eigentlich füttere ich die funktion mit einem generator... und weiter geht es dann auch als generator... also eine generator kette...

Also was fertiges gibt es dafür nicht? Hab in intertools auch nichts gefunden.

Wie meinst du das mit values nicht rausgeben? Also mit list() eine kopie machen? oder yield values.copy() ?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Man könnte das `pairwise()`-Beispiel aus der `itertools`-Doku etwas abwandeln:

Code: Alles auswählen

from itertools import izip, tee

def iter_steps(g, steps):
    """
    >>> for v in iter_steps([1,2,3,4], steps=2): v
    [1, 2]
    [2, 3]
    [3, 4]
    >>> for v in iter_steps([1,2,3,4,5], steps=3): v
    [1, 2, 3]
    [2, 3, 4]
    [3, 4, 5]
    """
    iterators = tee(g, steps)
    for idx, it in enumerate(iterators):
        for i in xrange(idx):
            next(it, None)
    for result in izip(*iterators):
        yield list(result)
Letztlich benutzt `tee()` aber intern auch so etwas wie eine Liste und ich kann auch ehrlich gesagt nicht einschätzen, ob der Einsatz von `tee()` hier wirklich sinnvoll ist.
Zuletzt geändert von snafu am Donnerstag 15. August 2013, 09:33, insgesamt 1-mal geändert.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab nun zwei funktionen:

Code: Alles auswählen

def iter_steps(g, steps):
    """
    >>> for v in iter_steps([1,2,3,4], steps=2): v
    [1, 2]
    [3, 4]
    >>> for v in iter_steps([1,2,3,4,5,6], steps=3): v
    [1, 2, 3]
    [4, 5, 6]
    """
    values = []
    for value in g:
        values.append(value)
        if len(values)==steps:
            yield list(values)
            values = []


def iter_window(g, steps):
    """
    >>> for v in iter_window([1,2,3,4], steps=2): v
    [1, 2]
    [2, 3]
    [3, 4]
    >>> for v in iter_window([1,2,3,4,5], steps=3): v
    [1, 2, 3]
    [2, 3, 4]
    [3, 4, 5]
    """
    values = []
    for value in g:
        values.append(value)
        if len(values)==steps:
            yield list(values)
            values.pop(0)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@jens: Bei `yield list(values)` ist das `list()` überflüssig. Das zweite Beispiel würde ich so nicht verwenden, da `.pop(0)` auf Listen böse ist. Du könntest `deque.popleft()` nutzen. Dieses verhält sich anders als "normale" Listen. Oder du steckst die Elemente in umgekehrter Reihenfolge in die Liste und machst dann ein `.pop()` (also letztes Element) runternehmen, was die Sache nochmal etwas komplizierter macht. Also wenn schon, dann nimm ne Deque.
BlackJack

@snafu: Zumindest bei `iter_window()` ist der `list()`-Aufruf nicht überflüssig. Wenn man den nicht macht, kann ein verändern der Liste ausserhalb des Iterators den Iterator selbst beeinflussen. Man würde dort internen Zustand nach aussen geben, an dem der Aufrufer nichts zu manipulieren hat.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Also so:

Code: Alles auswählen

def iter_window(g, steps):
    """
    >>> for v in iter_window([1,2,3,4], steps=2): v
    [1, 2]
    [2, 3]
    [3, 4]
    >>> for v in iter_window([1,2,3,4,5], steps=3): v
    [1, 2, 3]
    [2, 3, 4]
    [3, 4, 5]
    
    >>> for v in iter_window([1,2,3,4], steps=2):
    ...    v
    ...    v.append(True)
    [1, 2]
    [2, 3]
    [3, 4]
    """
    values = collections.deque()
    for value in g:
        values.append(value)
        if len(values)==steps:
            yield list(values)
            values.popleft()
Bei yield ein list() zu machen, ist, damit eine Kopie zurück geliefert wird. Dazu der zusätzliche DocTest.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Stimmt, das mit der Kopie hatte ich übersehen. Also ich find deine zuletzt gezeigte Lösung (mit der Deque) ganz okay.

Man sollte vielleicht noch bedenken, dass der `len()`-Check überflüssig wird (sofern ich nicht schon wieder was übersehen habe) in dem Moment, wenn die Deque zum ersten Mal komplett befüllt wurde.
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Irgendwann habe ich mal diesen Ansatz mit zip und iter aufgeschnappt. Kann man das nicht so machen?

Code: Alles auswählen

In [9]: ls = [1, 2, 3, 4, 5, 6]

In [10]: zip(*[iter(ls)] * 2)
Out[10]: [(1, 2), (3, 4), (5, 6)]

In [11]: zip(*[iter(ls)] * 3)
Out[11]: [(1, 2, 3), (4, 5, 6)]

In [12]: zip(*[iter(ls[i:]) for i in xrange(3)])
Out[12]: [(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)]

In [13]: zip(*[iter(ls[i:]) for i in xrange(2)])
Out[13]: [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]

In [14]: zip(*[iter(ls[i:]) for i in xrange(4)])
Out[14]: [(1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6)]
:wink:
yipyip
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@jens: Deques haben ein optionales `maxlen`-Attribut, welches beim Überschreiten der angegebenen Maximallänge, die Elemente am anderen Ende rauswirft. Das kannst du dir doch sicher zu Nutze machen und damit das explizite `.popleft()` einsparen.

@yipyip: Deine Lösung klappt zumindest auf Objekten, die Slicing unterstützen. So wie ich jens verstanden habe, ist das aber in seinem Fall nicht zwingend gegeben.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Stimmt beides:

Code: Alles auswählen

def iter_window(g, steps):
    """
    >>> for v in iter_window([1,2,3,4], steps=2): v
    [1, 2]
    [2, 3]
    [3, 4]
    >>> for v in iter_window([1,2,3,4,5], steps=3): v
    [1, 2, 3]
    [2, 3, 4]
    [3, 4, 5]

    >>> for v in iter_window([1,2,3,4], steps=2):
    ...    v
    ...    v.append(True)
    [1, 2]
    [2, 3]
    [3, 4]
    """
    values = collections.deque(maxlen=steps)
    for value in g:
        values.append(value)
        if len(values)==steps:
            yield list(values)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Den Test in der Schleife kannst du auch noch loswerden:

Code: Alles auswählen

def iter_window(g, steps):
    iterator = iter(g)
    values = collections.deque(itertools.islice(iterator, steps-1), maxlen=steps)
    
    for value in iterator:
        values.append(value)
        yield list(values)
Das Leben ist wie ein Tennisball.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

snafu hat geschrieben:Letztlich benutzt `tee()` aber intern auch so etwas wie eine Liste und ich kann auch ehrlich gesagt nicht einschätzen, ob der Einsatz von `tee()` hier wirklich sinnvoll ist.
`tee()` sollte nur die Differenz zwischen dem Iterator der am häufigsten und der am wenigsten ge-yielded hat speichern. Damit wäre man bei `steps` Elementen und die musst du sowieso vorhalten.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Hier noch eine Alternative zu »iter_steps«:

Code: Alles auswählen

def iter_steps(g, steps):
    iterators = [iter(g)]*steps
    for k in itertools.izip(*iterators):
        yield list(k)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sirius3 hat geschrieben:Hier noch eine Alternative zu »iter_steps«:

Code: Alles auswählen

def iter_steps(g, steps):
    iterators = [iter(g)]*steps
    for k in itertools.izip(*iterators):
        yield list(k)
Das tut allerdings nicht das, was gefordert ist.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

snafu hat geschrieben:Das tut allerdings nicht das, was gefordert ist.
Es geht hier tatsächlich um zwei Funktionen. Auf der ersten Seite zeigt jens iter_steps und iter_window. Etwas ungünstig, dass iter_steps als Name wiederverwendet wurde.
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Jep, weil ich später gemerkt habe, das ich doch beide Varianten brauchen kann.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten