Python:有效地检查整数是否在*many*范围内

Python:有效地检查整数是否在*many*范围内,python,Python,我正在开发一个邮资应用程序,该应用程序需要根据多个邮政编码范围检查整数邮政编码,并根据邮政编码匹配的范围返回不同的代码 每个代码有多个邮政编码范围。例如,如果邮政编码在1000-2429、2545-2575、2640-2686范围内或等于2890,则应返回M代码 我可以这样写: if 1000 <= postcode <= 2429 or 2545 <= postcode <= 2575 or 2640 <= postcode <= 2686 or postc

我正在开发一个邮资应用程序,该应用程序需要根据多个邮政编码范围检查整数邮政编码,并根据邮政编码匹配的范围返回不同的代码

每个代码有多个邮政编码范围。例如,如果邮政编码在1000-2429、2545-2575、2640-2686范围内或等于2890,则应返回M代码

我可以这样写:

if 1000 <= postcode <= 2429 or 2545 <= postcode <= 2575 or 2640 <= postcode <= 2686 or postcode == 2890:
    return 'M'
1米重复的时间:5.11秒

元组中的范围(Jeff Mercado) 在我看来更优雅一些,当然更容易进入和阅读范围。如果它们随着时间的推移而改变,这是可能的。但在我的实现中,它确实慢了四倍

if any(lower <= postcode <= upper for (lower, upper) in [(1000, 2249), (2555, 2574), ...]):
    return 'M'
if any(lower <= postcode <= upper for (lower, upper) in [(2250, 2265), ...]):
    return 'N'
...
1米重复的时间:339.35秒

对分(罗伯特·金) 这个可能比我的智力水平高了一点。我读了很多关于
bisect
模块的书,但就是不知道应该给
find_ge()
哪些参数来进行可运行的测试。我希望它会非常快,有许多邮政编码循环,但如果它每次都要进行设置的话就不会了。因此,对于一个邮政区号(M有四个范围的代码),只需重复填写
编号
边部路径
边部路径
等1m次,而不是实际运行
快速求解器

1米重复的时间:105.61秒

Dict(哨兵) 使用预先生成的每个邮政区号一个dict,将CPI打包到源文件(106 KB)中,并为每次运行加载。我本以为这种方法会有更好的性能,但至少在我的系统上,IO真的破坏了它。服务器是一款速度不太快的顶级MacMini

1米重复的时间:5895.18秒(根据10000次跑步推断)

总结 嗯,我希望有人能给出一个我没有考虑过的简单的“duh”答案,但事实证明这要复杂得多(甚至有点争议)

如果在这种情况下,每纳秒的效率都被计算在内,我可能会保持一个单独的进程运行,该进程实现了一个二进制搜索或dict解决方案,并将结果保存在内存中,以便进行极快的查找。然而,由于IF树只需5秒钟就可以运行一百万次,这对于我的小企业来说已经足够快了,因此我将在我的应用程序中使用它


谢谢大家的贡献

你真的制定了基准吗?这段代码的性能是否会影响整个应用程序的性能?所以,首先基准测试

但您也可以使用dict,例如用于存储“M”范围的所有键:


当然:散列需要更多内存,但访问时间是O(1)。

您可以将范围放入元组,然后将元组放入列表中。然后使用
any()
帮助您查找您的值是否在这些范围内

ranges = [(1000,2429), (2545,2575), (2640,2686), (2890, 2890)]
if any(lower <= postcode <= upper for (lower, upper) in ranges):
    print('M')
ranges=[(10002429),(2545255),(26402686),(28902890)]
如果任何(lowerPython有一个range(a,b)函数,表示从(包括)a到(但不包括)b的范围。您可以列出这些范围,并检查其中是否有数字。使用xrange(a,b)可能更有效,它具有相同的含义,但实际上不在内存中列出

list_of_ranges = []
list_of_ranges.append(xrange(1000, 2430))
list_of_ranges.append(xrange(2545, 2576))
for x in [999, 1000, 2429, 2430, 2544, 2545]:
    result = False
    for r in list_of_ranges:
        if x in r:
            result = True
            break
    print x, result

最快的可能是检查集合的成员

>>> from itertools import chain
>>> ranges = ((1000, 2429), (2545, 2575), (2640, 2686), (2890, 2890))
>>> postcodes = set(chain(*(xrange(start, end+1) for start, end in ranges)))
>>> 1000 in postcodes
True
>>> 2500 in postcodes
False
但这样做确实会占用更多内存,而且构建集合需要时间,因此,如果只构建一次集合以检查循环中的多个邮政编码,效果会更好

编辑:似乎不同的范围需要映射到不同的字母

>>> from itertools import chain
>>> ranges = {'M':((1000,2429), (2545,2575), (2640,2686), (2890, 2890)),
              # more ranges
              }
>>> postcodemap = dict((k,v) for v in ranges for k in chain(*imap(xrange, *zip(*ranges[v]))))    
>>> print postcodemap.get(1000)
M
>>> print postcodemap.get(2500)
None

警告这可能是过早的优化。对于大量范围,这可能是值得的,但在您的情况下可能不是。此外,尽管字典/集合解决方案将使用更多内存,但它们仍然可能是更好的选择

您可以对范围端点进行二进制搜索。如果所有范围都不重叠,这将很容易,但仍然可以对重叠范围进行搜索(通过一些调整)

执行查找最大匹配小于二进制搜索。这与查找最小匹配大于或等于(下限)二进制搜索相同,只是从结果中减去一个

在端点列表中使用半开项-即如果您的范围为1000..2429(含1000..2429),则使用值1000和2430。如果您得到的端点和起点具有相同的值(两个范围相互接触,因此没有间隙),则从列表中排除较低范围的端点

如果找到范围起点终点,则目标值在该范围内。如果找到范围终点,则目标值不在任何范围内

二进制搜索算法大致如下(不要期望它在未经编辑的情况下运行)

注意-在Python 3中,整数除法必须使用“/”除法运算符。在Python 2中,正常的“/”将起作用,但最好为Python 3做好准备

最后,上界和下界都指向找到的项-但是对于“上界”搜索。减去一得到所需的搜索结果。如果得到-1,则没有匹配范围


库中可能有一个进行上限搜索的二进制搜索例程,因此如果是这样的话,请选择该例程。要更好地了解二进制搜索的工作原理,请参见-不,我不要求进行上表决;-)

在执行不等式时,只需求解边情况和边情况之间的一个数字

e、 g.如果您在十个上进行以下测试:

10<20,10<15,10>8,10>12

它会给出正确的答案

但是请注意,最接近10的数字是8和12

这意味着9,10,11将给出ten所做的答案。。如果您没有太多的初始范围编号,并且它们是
list_of_ranges = []
list_of_ranges.append(xrange(1000, 2430))
list_of_ranges.append(xrange(2545, 2576))
for x in [999, 1000, 2429, 2430, 2544, 2545]:
    result = False
    for r in list_of_ranges:
        if x in r:
            result = True
            break
    print x, result
>>> from itertools import chain
>>> ranges = ((1000, 2429), (2545, 2575), (2640, 2686), (2890, 2890))
>>> postcodes = set(chain(*(xrange(start, end+1) for start, end in ranges)))
>>> 1000 in postcodes
True
>>> 2500 in postcodes
False
>>> from itertools import chain
>>> ranges = {'M':((1000,2429), (2545,2575), (2640,2686), (2890, 2890)),
              # more ranges
              }
>>> postcodemap = dict((k,v) for v in ranges for k in chain(*imap(xrange, *zip(*ranges[v]))))    
>>> print postcodemap.get(1000)
M
>>> print postcodemap.get(2500)
None
while upperbound > lowerbound :
  testpos = lowerbound + ((upperbound-lowerbound) // 2)

  if item [testpos] > goal :
    #  new best-so-far
    upperbound = testpos
  else :
    lowerbound = testpos + 1
def find_ge(a, key):
    '''Find smallest item greater-than or equal to key.
    Raise ValueError if no such item exists.
    If multiple keys are equal, return the leftmost.

    '''
    i = bisect_left(a, key)
    if i == len(a):
        raise ValueError('No item found with key at or above: %r' % (key,))
    return a[i]




ranges=[(1000,2429), (2545,2575), (2640,2686), (2890, 2890)]
numbers=[]
for pair in ranges:
        numbers+=list(pair)

numbers+=[-999999,999999] #ensure nothing goes outside the range
numbers.sort()
edges=set(numbers)

edgepairs={}

for i in range(len(numbers)-1):
        edgepairs[(numbers[i],numbers[i+1])]=(numbers[i+1]-numbers[i])//2



def slow_solver(x):
        return #your answer for postcode x


listedges=list(edges)
edgeanswers=dict(zip(listedges,map(solver,listedges)))
edgepairsanswers=dict(zip(edgepairs.keys(),map(solver,edgepairs.values())))

#now we are ready for fast solving:
def fast_solver(x):
        if x in edges:
                return edgeanswers[x]
        else:
                #find minL and maxS using find_ge and your own similar find_le
                return edgepairsanswers[(minL,maxS)]
import numpy as np
lows = np.array([1, 10, 100]) # the lower bounds
ups = np.array([3, 15, 130]) # the upper bounds

def in_range(x):
    return np.any((lows <= x) & (x <= ups))
in_range(2) # True
in_range(23) # False
#Sets the bits to one between lower and upper range 
def setRange(permitRange, lower, upper):
  # the range is inclusive of left & right edge. So add 1 upper limit
  bUpper = 1 << (upper + 1)
  bLower = 1 << lower
  mask = bUpper - bLower
  return (permitRange | mask)

#For my case the ranges also include single integers. So added method to set single bits
#Set individual bits  to 1
def setBit(permitRange, number):
  mask = 1 << vlan
  return (permitRange| mask)
#Example range (10-20, 25, 30-50)
rangeList = "10-20, 25, 30-50"
maxRange = 100
permitRange = 1 << maxRange
for range in rangeList.split(","):
    if range.isdigit():
        permitRange = setBit(permitRange, int(range))
    else:
        lower, upper = range.split("-",1)
        permitRange = setRange(permitRange, int(lower), int(upper))
    return permitRange
#return a non-zero result, 2**offset, if the bit at 'offset' is one.
def testBit(permitRange, number):
    mask = 1 << number
    return (permitRange & mask)

if testBit(permitRange,10):
    do_something()
ranges = (
    (1000, 2249, 'M'), 
    (2250, 2265, 'N'), 
    (2555, 2574, 'M'),
    # ...
)
def code_lookup(value, ranges):
    left, right = 0, len(ranges)

    while left != right - 1:
        mid = left + (right - left) // 2

        if value <= ranges[mid - 1][1]:  # Check left split max
            right = mid
        elif value >= ranges[mid][0]:    # Check right split min
            left = mid
        else:                            # We are in a gap
            return None

    if ranges[left][0] <= value <= ranges[left][1]:
        # Return the code
        return ranges[left][2]
def get_code_naive(value):
    if 1000 < value < 2249:
        return 'M'
    if 2250 < value < 2265:
        return 'N'
    # ...