Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3.x Shifting Ranges

suppose I have a range like this:

x = range(10)

which would have the following values as a list:

list(x)      # Prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

I would like to shift this range (possibly multiple times) and iterate over the results e.g.

             #        [7, 8, 9, 0, 1, 2, 3, 4, 5, 6]

Creating an equivalent list is not a problem. But I'd like to know if it is possible to create something like this as a range to save some space in memory and of course it would be nice if the solution could be about as performant as:

for i in range(1000000)
like image 547
Nima Mousavi Avatar asked Oct 27 '25 15:10

Nima Mousavi


1 Answers

You can wrap the range in a generator expression, applying the shift and modulo on the fly:

def shifted_range(rangeob, shift):
    size, shift = rangeob.stop, shift * rangeob.step
    return ((i + shift) % size for i in rangeob)

Demo:

>>> def shifted_range(rangeob, shift):
...     size, shift = rangeob.stop, shift * rangeobj.step
...     return ((i + shift) % size for i in rangeob)
... 
>>> range_10 = range(10)
>>> list(shifted_range(range_10, 3))
[3, 4, 5, 6, 7, 8, 9, 0, 1, 2]
>>> list(shifted_range(range_10, 7))
[7, 8, 9, 0, 1, 2, 3, 4, 5, 6]
>>> range_10_2 = range(0, 10, 2)
>>> list(shifted_range(range_10_2, 4))
[8, 0, 2, 4, 6]

You could make this a wrapper object as well:

class RangeShift:
    def __init__(self, rangeob, shift):
        self._range = rangeob
        self.shift = shift

    @property
    def start(self):
        r = self._range
        return (r.start + self.shift * r.step) % r.stop

    @property
    def stop(self):
        r = self._range
        return (r.stop + self.shift * r.step) % r.stop

    def index(self, value):
        idx = self._range.index(value)
        return (idx - self.shift) % len(self._range)

    def __getattr__(self, attr):
        return getattr(self._range, attr)

    def __getitem__(self, index):
        r = self._range
        return (r[index] + self.shift * r.step) % r.stop

    def __len__(self):
        return len(self._range)

    def __iter__(self):
        size, shift = self._range.stop, self.shift * self._range.step
        return ((i + shift) % size for i in self._range)

This will behave just like the original range, but applying a shift to all values produced. It even lets you alter the shift!

Demo:

>>> range_10 = range(10)
>>> shifted = RangeShift(range_10, 7)
>>> len(shifted)
10
>>> shifted.start
7
>>> shifted.stop
7
>>> shifted.step
1
>>> shifted[3]
0
>>> shifted[8]
5
>>> list(shifted)
[7, 8, 9, 0, 1, 2, 3, 4, 5, 6]
>>> shifted.shift = 3
>>> list(shifted)
[3, 4, 5, 6, 7, 8, 9, 0, 1, 2]
>>> range_10_2 = range(0, 10, 2)
>>> shifted_10_2 = RangeShift(range_10_2, 4)
>>> list(shifted_10_2)
[8, 0, 2, 4, 6]

Best trick this wrapper now supports: reversing the shifted range:

>>> list(reversed(shifted))
[2, 1, 0, 9, 8, 7, 6, 5, 4, 3]
>>> list(reversed(shifted_10_2))
[6, 4, 2, 0, 8]
like image 166
Martijn Pieters Avatar answered Oct 29 '25 04:10

Martijn Pieters



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!