Drudge Drudge - 26 days ago 7
Python Question

Python for loop with modulo

Is it possible to create a python for-loop with a modulo operation? I have a ringbuffer in Python and I want to iterate the elements between the

startPos
and
endPos
indexes, where
startPos
can have a bigger value than
endPos
. In other programming languages, I would intuitively implement this with a modulo operator:

int startPos = 6;
int endPos = 2;
int ringBufferSize = 8;
for(int i = startPos, i != endPos, i = (i+1) % ringBufferSize) {
print buffer.getElementAt(i);
}


Is there a way to do this easily in Python? I only found the

for i in list:
print buffer[i]


Syntax but nothing which provides an equivalent solution to my problem.

My next approach would be to create the list in advance before iterate the indexes which are stored in the list. But is there a way to do this as a one-liner like in other programming languages by using the modulo operation directly in the for loop?

Answer Source

You have some ways of doing that:

As you do in "other programing languages" (i.e. C derived syntaxes), just that you basically have to write their for loop in a while form - and then you realize that C's for is just a while nonetheless:

start_pos = 6
end_pos = 2
ring_buffer_size = 8
i = start_pos
while True:
    i = (i + 1) % ring_buffer_size
    if i <= end_pos:
        break
    # your code here

Now, for the for statement, Python only has what is called "for each" - which always walks an iterable or sequence. So you can create an iterable that will yield your values -

def ring(start, end, buffer_size, increment=1):
    i = start
    while i != end:
       yield i
       i += 1
       i %= buffer_size

for slot in ring(6, 2, 8):
    # your code here

Note that while this second form is "bigger", it does abstract away your circular buffer logic, avoiding that hard code values get mixed with their meaning where you don't need to look at them - that is, inside the for body itself.

Note that the actual idea of for in Python is to iterate over the buffer contents itself, not and index that will lead to its contents.
So, the Python standard library includes a ready made circular buffer object already that always have its indexes normalized to 0 and (len - 1) - just import deque from the collections module.

If you want a circular buffer with changing start and end indexes taht will wrap around and work automatically in forstatements, that is also relatively easy to do - if you don need the full functionality, just subclass list, add the start and end indexes, and make a custom implementation of its __iter__ method:

class Circular(list):

    def __init__(self, content, start, end):
        super(Circular, self).__init__( content)
        self.start = start
        self.end = end

    def __iter__(self):
        for i in range(self.start, self.start + len(self)):
            if i % len(self) == self.end: break
            yield self[i % len(self)]

And now you can use this custom container in your code:

In [22]: mylist = Circular(range(8), 6 , 2)

In [23]: for i in mylist:
    ...:     print(i)
    ...:     
6
7
0
1