32
votes

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?

12
What's wrong with just using reversed? You explicitly want to be able to go backwards, or just want the results backwards? - jathanism
Oh.... do you want to stop mid iteration and back up? You'll probably have to cache the part of the list that you have traversed and traverse it using reversed() - D.Shawley
I can do that, however that ends up leading to icky code, in C++ we have bidirectional iteration support. - UberJumper
@uberjumper: the concept of the iterator protocol being used by generators for lazily evaluated sequences makes generic bidirectional iterators a lot harder to do in Python. Your best bet is to index over the collection with a custom iterator that keeps track of the index and backs up like Tamás implemented. - D.Shawley

12 Answers

30
votes

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
10
votes

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
3
votes

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.

2
votes

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
1
votes

You can wrap your iterator in an iterator helper to enable it to go backward. It will store the iterated values in a collection and reuse them when going backwards.

class MemoryIterator:
    def __init__(self, iterator : Iterator):
        self._iterator : Iterator = iterator
        self._array = []
        self._isComplete = False
        self._pointer = 0

    def __next__(self):
        if self._isComplete or self._pointer < len(self._array):
            if self._isComplete and self._pointer >= len(self._array):
                raise StopIteration

            value = self._array[self._pointer]
            self._pointer = self._pointer + 1
            return value

        try:
            value = next(self._iterator)
            self._pointer = self._pointer + 1
            self._array.append(value)
            return value
        except StopIteration:
            self._isComplete = True

    def prev(self):        
        if self._pointer - 2 < 0:
            raise StopIteration

        self._pointer = self._pointer - 1
        return self._array[self._pointer - 1]

The usage can be similar to this one:

my_iter = iter(my_iterable_source)
memory_iterator = MemoryIterator(my_iter)
try:
    if forward:
        print(next(memory_iterator))
    else:
        print(memory_iterator.prev())
except StopIteration:
    pass
1
votes

You can enable an iterator to move backwards by following code.

class EnableBackwardIterator:
    def __init__(self, iterator):
        self.iterator = iterator
        self.history = [None, ]
        self.i = 0

    def next(self):
        self.i += 1
        if self.i < len(self.history):
            return self.history[self.i]
        else:
            elem = next(self.iterator)
            self.history.append(elem)
            return elem

    def prev(self):
        self.i -= 1
        if self.i == 0:
            raise StopIteration
        else:
            return self.history[self.i]

Usage:

>>> prev = lambda obj: obj.prev()  # A syntactic sugar.
>>> 
>>> a = EnableBackwardIterator(iter([1,2,3,4,5,6]))
>>> 
>>> next(a)
1
>>> next(a)
2
>>> a.next()  # The same as `next(a)`.
3
>>> prev(a)
2
>>> a.prev()  # The same as `prev(a)`.
1
>>> next(a)
2
>>> next(a)
3
>>> next(a)
4
>>> next(a)
5
>>> next(a)
6
>>> prev(a)
5
>>> prev(a)
4
>>> next(a)
5
>>> next(a)
6
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
0
votes

i think thi will help you to solve your problem

    class TestIterator():
        def __init__(self):`
            self.data = ["MyData", "is", "here","done"]
            self.index = -1
            #self.index=len(self.data)-1
    def __iter__(self):
        return self

    def next(self):
        self.index += 1
        if self.index >= len(self.data):
            raise StopIteration
        return self.data[self.index]

    def __reversed__(self):
        self.index = -1
        if self.index >= len(self.data):
            raise StopIteration
        return self.data[self.index]

r = TestIterator()
itr=iter(r)
print (next(itr))
print (reversed(itr))
0
votes
ls = [' a', 5, ' d', 7, 'bc',9, ' c', 17,  '43', 55, 'ab',22, 'ac']
direct = -1
l = ls[::direct]
for el in l:
    print el

Where direct is -1 for reverse or 1 for ordinary.

0
votes

Python you can use a list and indexing to simulate an iterator:

a = [1,2,3]

current = 1

def get_next(a):
  current = a[a.index(current)+1%len(a)]
  return current
def get_last(a):
  current = a[a.index(current)-1]
  return current # a[-1] >>> 3 (negative safe)

if your list contains duplicates then you would have to track your index separately:

a =[1,2,3]
index = 0

def get_next(a):
  index = index+1 % len(a)
  current = a[index]
  return current
def get_last(a):
  index = index-1 % len(a)
  current = a[index-1]
  return current # a[-1] >>> 3 (negative safe)
0
votes

An iterator that visits the elements of a list in reverse order:

class ReverseIterator:
    def __init__(self,ls):
        self.ls=ls
        self.index=len(ls)-1

    def __iter__(self):
        return self
    def __next__(self):
        if self.index<0:
            raise StopIteration
        result = self.ls[self.index]
        self.index -= 1
        return result
0
votes

I came here looking for a bi-directional iterator. Not sure if this is what the OP was looking for but it is one way to make a bi-directional iterator—by giving it an attribute to indicate which direction to go in next:

class BidirectionalCounter:
    """An iterator that can count in two directions (up
    and down).
    """

    def __init__(self, start):
        self.forward = True
        # Code to initialize the sequence
        self.x = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.forward:
            return self.next()
        else:
            return self.prev()
    
    def reverse(self):
        self.forward = not self.forward
        
    def next(self):
        """Compute and return next value in sequence.
        """
        # Code to go forward
        self.x += 1
        return self.x
    
    def prev(self):
        """Compute and return previous value in sequence.
        """
        # Code to go backward
        self.x -= 1
        return self.x

Demo:

my_counter = BidirectionalCounter(10)
print(next(my_counter))
print(next(my_counter))
my_counter.reverse()
print(next(my_counter))
print(next(my_counter))

Output:

11
12
11
10
-2
votes

please see this function made by Morten Piibeleht. It yields a (previous, current, next) tuple for every element of an iterable.

https://gist.github.com/mortenpi/9604377