views:

389

answers:

4

Is there anyway to make a python list iterator to go backwards?

Basically i have this

class IterTest(object):
    def __init__(self, data):
        self.data = data
        self.__iter = None

    def all(self):
        self.__iter = iter(self.data)
        for each in self.__iter:
            mtd = getattr(self, type(each).__name__)
            mtd(each)

    def str(self, item):
        print item

        next = self.__iter.next()
        while isinstance(next, int):
            print next
            next = self.__iter.next()

    def int(self, item):
        print "Crap i skipped C"

if __name__ == '__main__':
    test = IterTest(['a', 1, 2,3,'c', 17])
    test.all()

Running this code results in the output:

a
1
2
3
Crap i skipped C

I know why it gives me the output, however is there a way i can step backwards in the str() method, by one step?

EDIT

Okay maybe to make this more clear. I don't want to do a full reverse, basically what i want to know if there is an easy way to do the equivalent of a bidirectional iterator in python?

+2  A: 

No, in general you cannot make a Python iterator go backwards. However, if you only want to step back once, you can try something like this:

def str(self, item):
    print item

    prev, current = None, self.__iter.next()
    while isinstance(current, int):
        print current
        prev, current = current, self.__iter.next()

You can then access the previous element any time in prev.

If you really need a bidirectional iterator, you can implement one yourself, but it's likely to introduce even more overhead than the solution above:

class bidirectional_iterator(object):
    def __init__(self, collection):
        self.collection = collection
        self.index = 0

    def next(self):
        try:
            result = self.collection[self.index]
            self.index += 1
        except IndexError:
            raise StopIteration
        return result

    def prev(self):
        self.index -= 1
        if self.index < 0:
            raise StopIteration
        return self.collection[self.index]

    def __iter__(self):
        return self
Tamás
Yeah i am trying to to avoid this however, since this will add a fair bit of annoying overhead :/
UberJumper
Added a `bidirectional_iterator` example above since I've seen that you've updated your question, but this is likely to introduce even more overhead than my first solution.
Tamás
+1  A: 

An iterator is by definition an object with the next() method -- no mention of prev() whatsoever. Thus, you either have to cache your results so you can revisit them or reimplement your iterator so it returns results in the sequence you want them to be.

kaloyan
+1  A: 

Am I missing something or couldn't you use the technique described in the Iterator section in the Python tutorial?

>>> class reverse_iterator:
...     def __init__(self, collection):
...         self.data = collection
...         self.index = len(self.data)
...     def __iter__(self):
...         return self
...     def next(self):
...         if self.index == 0:
...             raise StopIteration
...         self.index = self.index - 1
...         return self.data[self.index]
...     
>>> for each in reverse_iterator(['a', 1, 2, 3, 'c', 17]):
...     print each
... 
17
c
3
2
1
a

I know that this doesn't walk the iterator backwards, but I'm pretty sure that there is no way to do that in general. Instead, write an iterator that walks a discrete collection in reverse order.

Edit you can also use the reversed() function to get a reversed iterator for any collection so that you don't have to write your own:

>>> it = reversed(['a', 1, 2, 3, 'c', 17])
>>> type(it)
<type 'listreverseiterator'>
>>> for each in it:
...  print each
... 
17
c
3
2
1
a
D.Shawley
A simpler and equivalent solution to this one would be to use the `reversed` built-in which returns an iterator that walks over a connection in reversed order.
Tamás
@Tamás: I was writing the edit just as you mentioned it. I didn't think about it until after I posted the first response.
D.Shawley
I don't want to reverse the entire iterator i am looking for the equivalent of a bidirectional iterator within python.
UberJumper
I would like to know exactly what problem is solved *exclusively* by the concept of a bidirectional iterator, and *can't* be solved by `reversed()`
Bryan Ross
@BryanRoss: my guess is that the algorithm will back up by _n_ places when it encounters a certain value. This can be solved by recording the sequence as you traverse it and use `reversed()` on the memorized sequence to back up a few steps.
D.Shawley
@D.Shawley: See? There ya go. You solved it using reversed (which was my point). This smells uncomfortably like premature optimization.
Bryan Ross
A: 

Based on your question, it sounds like you want something like this:

class buffered:
    def __init__(self,it):
        self.it = iter(it)
        self.buf = []
    def __iter__(self): return self
    def __next__(self):
        if self.buf:
            return self.buf.pop()
        return next(self.it)
    def push(self,item): self.buf.append(item)

if __name__=="__main__":
    b = buffered([0,1,2,3,4,5,6,7])
    print(next(b)) # 0
    print(next(b)) # 1
    b.push(42)
    print(next(b)) # 42
    print(next(b)) # 2
David X