Seite 1 von 1

iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:00
von jens
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?

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:09
von 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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:20
von jens
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() ?

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:32
von snafu
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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:33
von jens
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)

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:40
von snafu
@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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:48
von 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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:53
von jens
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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 09:57
von snafu
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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 10:01
von yipyip
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

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 10:08
von snafu
@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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 10:35
von jens
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)

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 10:44
von EyDu
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)

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 12:15
von DasIch
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.

Re: iter steps ?!?!

Verfasst: Donnerstag 15. August 2013, 23:36
von Sirius3
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)

Re: iter steps ?!?!

Verfasst: Freitag 16. August 2013, 06:30
von snafu
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.

Re: iter steps ?!?!

Verfasst: Freitag 16. August 2013, 09:09
von EyDu
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.

Re: iter steps ?!?!

Verfasst: Freitag 16. August 2013, 09:25
von jens
Jep, weil ich später gemerkt habe, das ich doch beide Varianten brauchen kann.