Making a python iterator go backwards?

Question:

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?

Asked By: UberJumper

||

Answers:

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
Answered By: Tamás

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.

Answered By: ktdrv

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
Answered By: D.Shawley

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
Answered By: David X

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))
Answered By: dilshad

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
Answered By: jkulhanek
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.

Answered By: robert tuw

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)
Answered By: Josh Sharkey

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

Answered By: Raki Lachraf

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
Answered By: AnnieFromTaiwan

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
Answered By: zhixuan song

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
Answered By: Bill

I edited the python code from dilshad (thank you) and used the following Python 3 based code to step between list item’s back and forth or let say bidirectional:

# bidirectional class
class bidirectional_iterator:
def __init__(self):
    self.data = ["MyData", "is", "here", "done"]
    self.index = -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 == -1:
        raise StopIteration
    return self.data[self.index]

Example:

>>> r = bidirectional_iterator()
>>> itr=iter(r)
>>> print (next(itr))
MyData
>>> print (next(itr))
is
>>> print (next(itr))
here
>>> print (reversed(itr))
is
>>> print (reversed(itr))
MyData
>>> print (next(itr))
is
Answered By: andi

This is a common situation when we need to make an iterator go back one step. Because we should get the item and then check if we should break the loop. When breaking the loop, the last item may be requied in later usage.

Except of implementing an iteration class, here is a handy way make use of builtin itertools.chain :

from itertools import chain

>>> iterator = iter(range(10))
>>> for i in iterator:
...     if i <= 5:
...         print(i)
...     else:
...         iterator = chain([i], iterator)  # push last value back
...         break
...         
0
1
2
3
4
5
>>> for i in iterator:
...     print(i)
...     
6
7
8
9
Answered By: jayvynl
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.