Seite 1 von 1

itertools.cycle - jetzt auch rückwärts

Verfasst: Donnerstag 7. Mai 2009, 20:02
von derdon
Ich habe mir eine Klasse geschrieben, um iterables nicht nur vorwärts durchlaufen zu können (mit der itertools.cycle Funktion), sondern auch rückwärts. Hier der Code: http://paste.pocoo.org/show/116157/. Und hier ein Beispiel, wie man diese Klasse einsetzen kann:

Code: Alles auswählen

In [594]: numbers = Cycler(('one', 'two', 'three', 'four', 'five'))

In [595]: numbers.next()
Out[595]: 'one'

In [596]: numbers.next()
Out[596]: 'two'

In [597]: numbers.prev()
Out[597]: 'one'

In [598]: numbers.prev()
Out[598]: 'five'

In [599]: numbers.prev()
Out[599]: 'four'

In [600]: numbers.prev()
Out[600]: 'three'
Eine wichtige Bedingung ist, dass es keine Duplikate im iterable geben darf. Sonst passiert z.B. folgendes, weil sich die Klasse immer den ersten gefunden Wert als current_value speichert:

Code: Alles auswählen

In [608]: letters = Cycler(['a', 'b', 'a', 'd'])

In [609]: letters.next()
Out[609]: 'a'

In [610]: letters.next()
Out[610]: 'b'

In [611]: letters.next()
Out[611]: 'a'

In [612]: letters.prev()
Out[612]: 'd'
Jemand eine Idee, wie man das vermeiden kann?

Verfasst: Donnerstag 7. Mai 2009, 20:14
von birkenfeld
Ich würde gar nicht auf cycle() zurückgreifen, sondern den aktuellen Index selbst mitzählen.

Verfasst: Donnerstag 7. Mai 2009, 21:27
von derdon
Gute Idee. Jetzt sieht es so aus (lästige Wiederholungen sind natürlich Absicht ;)): http://paste.pocoo.org/show/116172/ Damit ist mein Problem gelöst. Ein wenig mehr Ästhetik kann der Code natürlich noch vertragen.

Verfasst: Donnerstag 7. Mai 2009, 21:45
von Trundle
Wobei die Bezeichnung "iterable" recht irreführend ist, da Iteratoren kein Indexing können müssen und die allerwenigsten das unterstützen.

Code: Alles auswählen

>>> iterable = iter([])
>>> iterable[0]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: 'listiterator' object is unsubscriptable

Verfasst: Donnerstag 7. Mai 2009, 22:14
von EyDu
Und "Cycler" ist auch nicht unbedingt der richige Name:

Code: Alles auswählen

>>> l = [1,2]
>>> c = Cycler(l)
>>> c.next()
1
>>> c.next()
2
>>> c.next()
1
>>> c.next()
2
>>> c.next()
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    c.next()
  File "C:/Programme/Python30/test.py", line 18, in next
    return self.iterable[self.current_index - len(self.iterable)]
IndexError: list index out of range
>>> 
Und etwas gekürzt:

Code: Alles auswählen

class Cycler(object):
    """cycle through a list — both forward and backward!"""
    def __init__(self, iterable):
        self.iterable = iterable
        self.current_index = 0

    def step(self, direction):
        result = self.iterable[self.current_index]
        self.current_index = (self.current_index + direction) % len(self.iterable)
        return result
    
    def next(self):
        return self.step(1)

    def prev(self):
        return self.step(-1)

Verfasst: Freitag 8. Mai 2009, 13:58
von derdon
Was für einen Namen könnte man denn statt iterable nehmen? indexable_object hört sich auch nicht gut an, finde ich. EyDu: Danke für den Hinweis und deinen Code. Das sieht schon viel eleganter aus; diese Klasse werde ich in meinem Skript benutzen.

Verfasst: Freitag 8. Mai 2009, 14:55
von EyDu
Indexierbar reicht als Eigenschaft auch noch nicht aus: es muss irgend eine Ordnung zwischen den Indizes bestehen, hier von 0 bis n-1 und die Länge muss ebenfalls bekannt sein. Schreib die benötigten Eigenschaften doch so in die Dokumentation und erwähne als Beispiel Listen und Tupel. Dann weiß schon jeder, was gemeint ist.

Verfasst: Freitag 8. Mai 2009, 15:11
von derdon
ok, das schreibe ich dann in den docstring von __init__. Aber welchen Namen sollte ich für den Parameter wählen?

Verfasst: Freitag 8. Mai 2009, 15:25
von EyDu
Sequence sollte passen. Da hätte man auch früher drauf kommenkönnen ^^

Verfasst: Freitag 8. Mai 2009, 15:51
von derdon
Stimmt, damit fällt dann auch der docstring in __init__ weg. Super!