在元组列表中查找值的更快的方法是什么?

时间:2012-03-17 17:47:57

标签: python search sorting tuples

我正在通过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行意味着需要几天时间。

2 个答案:

答案 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),但空间复杂性会受到影响。

  1. 数据必须是静态的;必须在任何查找之前处理所有数据。
  2. 鉴于任意IP,必须有办法确定其significant octets
  3. 必须有足够的空间为每个国家/地区的每个重要值添加密钥。
  4. 以下是一个非常天真的实现。无论如何,它都选择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