如何检查一个数字是否在一组非连续范围内?

时间:2012-11-28 06:47:17

标签: python range

问题可能听起来很复杂,但实际上非常简单,但我在Python中找不到任何好的解决方案。

我的范围很像 ("8X5000", "8X5099")。此处X可以是任意数字,因此我希望匹配属于某个范围(805000..805099815000..815099或... 895000..895099)的数字。

我该怎么做?

3 个答案:

答案 0 :(得分:3)

@TimPietzcker的答案是正确的和Pythonic,但它提出了一些性能问题(可以说它更像Pythonic)。它创建一个搜索值的迭代器。我不希望Python能够优化搜索。

这应该表现得更好:

def IsInRange(n, r=("8X5000", "8X5099")):
    (minr, maxr) = [[int(i) for i in l.split('X')] for l in r]
    p = len(r[0]) - r[0].find('X')

    nl = (n // 10**p, n % 10**(p-1))
    fInRange = all([minr[i] <= nl[i] <= maxr[i] for i in range(2)])
    return fInRange

函数内部的第二行是嵌套列表理解,因此可能有点难以阅读,但它设置了:

minr = [8, 5000]
maxr = [8, 5099]

当n = 595049时:

nl = (5, 5049)

代码只是将范围拆分为多个部分(转换为int时),将目标数字拆分为多个部分,然后范围检查部分。在范围说明符中处理多个X是不难的。

更新

我刚刚使用timeit测试了相对性能:

def main():
    t1 = timeit.timeit('MultiRange.in_range(985000)', setup='import MultiRange', number=10000)
    t2 = timeit.timeit('MultiRange.IsInRange(985000)', setup='import MultiRange', number=10000)

    print t1, t2
    print float(t2)/float(t1), 1 - float(t2)/float(t1)        

在我运行Python 2.7.2的32位Win 7机器上,我的解决方案几乎是@TimPietzcker的10倍(具体来说,它在12%的时间内运行)。当你增加范围的大小时,它只会变得更糟。当:

ranges=("8X5000", "8X5999")

性能提升50倍。即使是最小的范围,我的版本运行速度也要快4倍。

使用@PaulMcGuire建议的in_range性能补丁,我的版本运行速度提高了3倍。

更新2

在@PaulMcGuire的评论的推动下,我继续将我们的功能重构为课程。这是我的:

class IsInRange5(object):
    def __init__(self, r=("8X5000", "8X5099")):
        ((self.minr0, self.minr1), (self.maxr0, self.maxr1)) = [[int(i) for i in l.split('X')] for l in r]
        pos = len(r[0]) - r[0].find('X')
        self.basel = 10**(pos-1)
        self.baseh = self.basel*10
        self.ir = range(2)

    def __contains__(self, n):
        return self.minr0 <= n // self.baseh <= self.maxr0 and \
            self.minr1 <= n % self.basel <= self.maxr1

这确实缩小了差距,但即使在预计算范围不变量(对于两者)之后,@ PaulMcGuire的时间也延长了50%。

答案 1 :(得分:1)

range = (80555,80888)

x = 80666

print range[0] < x < range[1]

也许你在寻找......

答案 2 :(得分:1)

Python 3的示例(在Python 2中,使用xrange而不是range):

def in_range(number, ranges=("8X5000", "8X5099")):
    actual_ranges = ((int(ranges[0].replace("X", digit)),
                     int(ranges[1].replace("X", digit)) + 1)
                     for digit in "0123456789")
    return any(number in range(*interval) for interval in actual_ranges)

结果:

>>> in_range(805001)
True
>>> in_range(895099)
True
>>> in_range(805100)
False

Paul McGuire建议对此进行改进(谢谢!):

def in_range(number, ranges=("8X5000", "8X5099")):
    actual_ranges = ((int(ranges[0].replace("X", digit)),
                     int(ranges[1].replace("X", digit)))
                     for digit in "0123456789")
    return any(minval <= number <= maxval for minval, maxval in actual_ranges)