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