Python 3.x 移位范围

4

假设我有这样一个范围:

x = range(10)

将具有以下值的列表:

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

我想移动这个范围(可能是多次),并迭代结果,例如:
             #        [7, 8, 9, 0, 1, 2, 3, 4, 5, 6]

创建一个等价的列表并不是问题。但我想知道是否有可能创建一个类似于范围的东西,以节省内存空间,并且如果解决方案能够达到以下性能水平就更好了:

for i in range(1000000)
4个回答

8
你可以将范围包装在生成器表达式中,实时应用偏移和取模操作:
def shifted_range(rangeob, shift):
    size, shift = rangeob.stop, shift * rangeob.step
    return ((i + shift) % size for i in rangeob)

演示:

>>> 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]

您可以将它作为一个包装对象来使用:

您可以将其视为一个包装器对象:

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)

这将像原始范围一样运作,但会对所有生成的值应用一个偏移量。它甚至允许您更改偏移量! 演示:
>>> 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]

现在这个包装器支持的最佳技巧:反转移位范围:

>>> list(reversed(shifted))
[2, 1, 0, 9, 8, 7, 6, 5, 4, 3]
>>> list(reversed(shifted_10_2))
[6, 4, 2, 0, 8]

感谢您提供这么详细的答案。(我猜至少有一个包含感谢的评论不会被视为垃圾邮件):P - Nima Mousavi

2

这是一个老问题,但我刚刚偶然发现了它。 我的解决方案:

  offset = 7
  shifted_range = range(x.start + offset, x.stop + offset, x.step)

1
我猜最简单的方法是将两个范围 chain 起来:
from itertools import chain

shifted = chain(range(7, 10), range(7))
for x in shifted:
    print(x)

1
你可以使用itertools来连接两个范围。即使这些范围的步长大于1,此代码也可以正常工作。
import itertools

def shift_range(r, s):
    return itertools.chain(range(r.start + s*r.step, r.stop, r.step), 
                           range(r.start, r.start + s*r.step, r.step))

测试:
>>> list(shift_range(range(10), 5))
[5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
>>> list(shift_range(range(3, 30, 3), 5))
[18, 21, 24, 27, 3, 6, 9, 12, 15]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接