Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rewinding iteration [duplicate]

I've been looping through the contents of a list in the simple fashion to take user input:

for n in [ 1, 2, 3, 4 ]:
 command = raw_input ( "%d >> " % (n) )
 ...

I want to implement an undo feature, which would mean "rewinding" the iteration back to the previous value. Here is a naive way which does decrease the value of n, but then skips the original value because the internal pointer into the list isn't altered:

for n in [ 1, 2, 3, 4 ]:
 if f(n):
  n -= 1
 ...

In the docs I see an iterator.next(), but no iterator.last(). I think I could switch to accessing list members by integer index and hand-roll the loop by manipulating the index myself, which isn't all that threatening, but is there a better way?

like image 646
Aaron Brick Avatar asked Jun 09 '26 06:06

Aaron Brick


2 Answers

There's no obvious way to do this in stock Python, but it's easy enough to make your own iterator that supports backtracking. For example, a "seekable" iterator follows. Seek relative 0 to repeat the same element, or relative -1 to go back to previous element.

NB because this style of iteration doesn't enjoy "guaranteed forward progress," it's arguably more prone to infinite loops.

class SeekableIterator(object):
    """An iterator that supports seeking backwards or forwards."""

    def __init__(self, iterable):
        """Make a SeekableIterator over an iterable collection."""

        self.iterable = iterable
        self.index = None

    def __iter__(self):
        """Start the iteration."""

        self.index = 0
        return self

    def next(self):
        """Return the next item in the iterator."""

        try:
            value = self.iterable[self.index]
            self.index += 1
            return value
        except IndexError:
            raise StopIteration

    def seek(self, n, relative=False):
        """Adjust the loop counter, either relatively or to an absolute index.
        Note that seeking 0 replays the current item. Seeking -1 goes to
        the previous item. If the adjustment pushes the index outside the
        iterable's bounds, raise an index error."""

        if relative:
            self.index += n - 1
            # NB index already advanced one in next(), so subtracting one here
        else:
            self.index = n
        if self.index < 0 or self.index >= len(self.iterable):
            raise IndexError


if __name__ == '__main__':

    import random

    def prob(percent):
        """Return True with roughly the given probability, else False"""
        return random.random() <= (percent * 1.0 / 100.0)

    seeker = SeekableIterator([1, 2, 3, 4])
    for n in seeker:
        print "n:", n

        if prob(50):
            if prob(50):
                print "\tREDO - seeking 0"
                seeker.seek(0, relative=True)
            elif n > 1:
                print "\tUNDO - seeking -1"
                seeker.seek(-1, relative=True)

Here is an example output:

n: 1
n: 2
n: 3
n: 4
    REDO - seeking 0
n: 4
    REDO - seeking 0
n: 4
    UNDO - seeking -1
n: 3
n: 4
like image 103
Jonathan Eunice Avatar answered Jun 11 '26 18:06

Jonathan Eunice


Use a While loop:

lis = [1, 2, 3, 4]
i = 0
while i < len(lis):
    if some_condition:
        print(lis[i])
        i += 1
    elif some_other_condition:
        print(lis[i])
        i -= 1
like image 30
Ashwini Chaudhary Avatar answered Jun 11 '26 20:06

Ashwini Chaudhary



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!