Pre-compute len(range(start, stop,step) )
在实际调用之前,如何计算调用range(start, stop, step)所产生的元素数?
上下文是我正在实现一个对象的切片索引
1 2 3 4 5 6 7 8
| def __init__(self, impl_object):
self.impl=impl_object # the object that actually holds (or generates) an array of values
def __getitem__(self, key):
if isinstance(key, slice):
(start,stop,step)=key.indices( self.impl.numValues() )
# It would be nice to know how many items I'm dealing with
# here
...snip... |
我已经说服自己,对于step>0len(range(start,stop,step))==(start-stop+step-1)/step
但我不知道如何将其概括为否定步骤。
编辑:我要求(强烈建议)解决方案需要花费O(1)时间。
最简单的方法是
1
| len(xrange(start, stop, step)) |
xrange.__len__计算它将yield计算的元素数,而不在内存中构造范围。
- 那不需要时间吗?(我知道我问的时候不清楚)。
- @戴夫:不,xrange从不构造范围。试试len(xrange(1000000000000000000))。
- 你能解释一下它是怎么工作的吗?似乎len(xrange)的发动机罩下发生了类似的短路。
- @戴夫:xrange知道你要找的公式:)一个xrange对象基本上是一个(start, stop, step)三重体,其方法使它看起来像一个整数列表。
- 证据:next(xrange(1,10))抛出一个typeerror,声明xrange对象不是迭代器。
- xrange文档注意到cpython实现细节限制了参数的大小和元素的数量。因此,这种方法似乎也会限制对象可以支持的切片。可能是问题,也可能不是问题,但可能应该考虑。
- @Michaelj.Barber:是的,但是如果最终要构造一个range,那么对于非常大的整数来说失败可能是件好事。
- @拉尔斯曼可能是,但我们对这个物体的了解还不够,无法说明它应该如何失败。我认为,更大的问题应该是像n=1000000000000000000000000000; len(xrange(n, n+2))这样的东西,它作为一个范围很好,但是作为xrange失败。我从来没有理由这么做…
如果您的start、stop和step与xrange的特定于实现的限制一致(例如cpython 2.x要求它们是"短的"python整数),您可以通过调用来获取range(start, stop, step)将包含的值的数量:
1
| len(xrange(start, stop, step)) |
在引擎盖下,xrange()调用返回xrange对象:
XRange objects have very little behavior: they only support indexing,
iteration, and the len() function.
len()调用为o(1),因为xrange类型根据传递的参数进行计算实现__len__方法,len()发现传递给它的对象实现__len__并调用它来获取长度。
如果你的start、stop和step可能不适用于xrange(),这里有一个功能(在黑客新闻讨论的帮助下实现xrange的博客文章值得称赞):
1 2
| def len_range(start, stop, step):
return max(0, (stop - start) // step + bool((stop - start) % step)) |
- pypy的get_len_of_range()可能更具战斗测试性:bitback.org/pypypy/pypypy/src/default/pypypy/module/builtin_uuuuuu/‌&8203;…
- 也就是说,上面的len_range实现可能有Pypy的get_len_of_range()实现没有的bug。
尝试使用abs(步骤)而不是步骤。