我正在通过ip范围查找国家数千万行。我正在寻找一种更快的查找方式。
我有这种形式的180K元组:
>>> data = ((0, 16777215, 'ZZ'),
... (1000013824, 1000079359, 'CN'),
... (1000079360, 1000210431, 'JP'),
... (1000210432, 1000341503, 'JP'),
... (1000341504, 1000603647, 'IN'))
(整数是将IP地址转换为普通数字。)
这样做的工作正确,但只需要很长时间:
>>> ip_to_lookup = 999
>>> country_result = [country
... for (from, to, country) in data
... if (ip_to_lookup >= from) and
... (ip_to_lookup <= to)][0]
>>> print country_result
ZZ
有人能指出我正确的方向来更快地进行此查找吗?使用上述方法,100次查找需要3秒。我想,10M行意味着需要几天时间。
答案 0 :(得分:8)
在对数据集进行排序后,您可以使用bisect
模块执行二进制搜索:
from operator import itemgetter
import bisect
data = ((0, 16777215, 'ZZ'), (1000013824, 1000079359, 'CN'), (1000079360, 1000210431, 'JP'), (1000210432, 1000341503, 'JP'), (1000341504, 1000603647, 'IN'))
sorted_data = sorted(data, key=itemgetter(0))
lower_bounds = [lower for lower,_,_ in data]
def lookup_ip(ip):
index = bisect.bisect(lower_bounds, ip) - 1
if index < 0:
return None
_, upper, country = sorted_data[index]
return country if ip <= upper else None
print(lookup_ip(-1)) # => None
print(lookup_ip(999)) # => 'ZZ'
print(lookup_ip(16777216)) # => None
print(lookup_ip(1000013824)) # => 'CN'
print(lookup_ip(1000400000)) # => 'IN'
此处的查询算法复杂度为O(log n)
,而不是O(n)
,以便完整列表行走。
答案 1 :(得分:1)
假设您的情况符合某些要求,有一种方法可以将运行时复杂度平均提高到O(1)
,但空间复杂性会受到影响。
以下是一个非常天真的实现。无论如何,它都选择IP的前两个八位字节,然后将重要的八位字节连接为整数,并逐渐为最小值和最大值之间的每个值添加一个键。你可以说,还有很大的改进空间。
from socket import inet_ntoa
from struct import pack
data = ((0, 16777215, 'ZZ'),
(1000013824, 1000079359, 'CN'),
(1000079360, 1000210431, 'JP'),
(1000210432, 1000341503, 'JP'),
(1000341504, 1000603647, 'IN'))
def makedict(minip, maxip, country):
d = {}
for i in xrange(key(minip), key(maxip)+1):
d[i] = country
return d
def key(ip):
octets = inet_ntoa(pack('>L', ip)).split('.')
return int("".join(octets[0:2]));
lookup = {}
for lo, hi, cnt in data:
lookup.update(makedict(lo, hi, cnt))
print lookup[key(999)] # ZZ
print lookup[key(1000215555)] # JP