繁体   English   中英

从 IP 地址范围获取城市

[英]Getting City from IP Address range

  1. 我有一个 IP 地址。 例如,192.168.2.10
  2. 我还有一本字典:
RANGES = {
        'london': [
            {'start': '10.10.0.0', 'end': '10.10.255.255'},
            {'start': '192.168.1.0', 'end': '192.168.1.255'},
        ],
        'munich': [
            {'start': '10.12.0.0', 'end': '10.12.255.255'},
            {'start': '172.16.10.0', 'end': '172.16.11.255'},
            {'start': '192.168.2.0', 'end': '192.168.2.255'},
        ]
    }

问题:我应该如何从我的 IP 地址找到城市并使用这本词典花费尽可能少的时间(时间复杂度)?

如果你想要任意大数据集的最佳复杂性,“正确答案”是季斌给出的答案。

要真正优化多次调用的性能,您确实需要重组数据,并使用内置的二分法 function。

但是如果你真的不想触及你的数据,你仍然可以使用 bisect 的创可贴自定义实现,它看起来像那样

RANGES = {
    'london': [
        {'start': '10.10.0.0', 'end': '10.10.255.255'},
        {'start': '192.168.1.0', 'end': '192.168.1.255'},
    ],
    'munich': [
        {'start': '10.12.0.0', 'end': '10.12.255.255'},
        {'start': '172.16.10.0', 'end': '172.16.11.255'},
        {'start': '192.168.2.0', 'end': '192.168.2.255'},
    ]
}


def ipv4_str_to_tuple(ip_str):
    return tuple(map(int, ip_str.split('.')))


def relative_in_range(ipv4_tuple, ip_range):
    ipv4t_start = ipv4_str_to_tuple(ip_range['start'])
    ipv4t_end = ipv4_str_to_tuple(ip_range['end'])
    if ipv4t_start > ipv4_tuple:
        return -1
    if ipv4t_end < ipv4_tuple:
        return 1
    return 0


def from_those_ranges(ipv4_tuple, ranges):
    #in-built bisect
    lo, hi = 0, len(ranges)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        comp = relative_in_range(ipv4_tuple, ranges[mid])
        if comp == 0:
            return True
        if comp > 0:
            lo = mid + 1
        else:
            hi = mid
    return False


def find_entry_from_ipv4_tuple(ipv4_tuple, entries_ranges):
    for entry, entry_ranges in entries_ranges.items():
        if from_those_ranges(ipv4_tuple, entry_ranges):
            return entry
    return None


def find_entry_from_ipv4_str(ipv4_str, entries_ranges):
    ipv4_tuple = ipv4_str_to_tuple(ipv4_str)
    return find_entry_from_ipv4_tuple(ipv4_tuple, entries_ranges)


print(find_entry_from_ipv4_str('10.2.4.2', RANGES))
print(find_entry_from_ipv4_str('192.168.2.1', RANGES))
print(find_entry_from_ipv4_str('192.168.1.1', RANGES))
print(find_entry_from_ipv4_str('172.12.10.25', RANGES))
print(find_entry_from_ipv4_str('192.168.2.1', RANGES))
print(find_entry_from_ipv4_str('10.10.5.5', RANGES))

-> 无

-> 慕尼黑

-> 伦敦

-> 无

-> 慕尼黑

-> 伦敦

等等

操场链接: https://trinket.io/python/e1f9deb1c7

编写自定义 function 将 IP 地址解析为数字元组以便于比较:

def get_city(ip):
    for city in RANGES:
        for d in RANGES[city]:
            if tuple(map(int, d["start"].split("."))) <= tuple(map(int, ip.split("."))) <= tuple(map(int, d["end"].split("."))):
                return city

>>> get_city("192.168.2.10")
"munich"

首先,您需要重新排列数据,以便更有效地查找。

  • 创建一个 function 用于将 IP 地址转换为数字
  • 并使用较低/开始的 IP 号作为新的数据键,并在值中保留结束 IP。
def ip_to_long(ip):
    return reduce(lambda x, y: (x << 8) + y, map(int, ip.split('.')))

def data_transform(input_ranges):
    data = {}
    for location, items in RANGES.items():
        for item in items:
            data[ip_to_long(item['start'])] = dict(location=location, end=ip_to_long(item['end']))

现在,您可以使用bisect来搜索已排序的开始 IP,对于您的输入,AIK 在内部使用 RB 树。

下面是它的完整 PoC 代码:

from functools import reduce
from bisect import bisect_left


RANGES = {
        'london': [
            {'start': '10.10.0.0', 'end': '10.10.255.255'},
            {'start': '192.168.1.0', 'end': '192.168.1.255'},
        ],
        'munich': [
            {'start': '10.12.0.0', 'end': '10.12.255.255'},
            {'start': '172.16.10.0', 'end': '172.16.11.255'},
            {'start': '192.168.2.0', 'end': '192.168.2.255'},
        ]
    }


def ip_to_long(ip):
    return reduce(lambda x, y: (x << 8) + y, map(int, ip.split('.')))

def data_transform(input_ranges):
    data = {}
    for location, items in input_ranges.items():
        for item in items:
            data[ip_to_long(item['start'])] = dict(location=location, end=ip_to_long(item['end']))
    return data

def search_for_ip(search_ip, ip_starts, ip_data):
    lookup_index = bisect_left(ip_starts, ip_to_long(search_ip))
    if lookup_index > 0 and ip_data[ip_starts[lookup_index-1]]['end'] > ip_to_long(search_ip):
        return ip_data[ip_starts[lookup_index-1]]['location']
    return

new_data = data_transform(RANGES)
print(new_data)

ip_starts = sorted(list(new_data))


print(search_for_ip('192.168.2.100', ip_starts, new_data))  # -> munich
print(search_for_ip('192.168.1.100', ip_starts, new_data))  # -> lodon
print(search_for_ip('192.168.0.100', ip_starts, new_data))  # -> None

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM