在python中,有没有更好的方法来迭代两个列表以查找项目之间的关系?

在python中,有没有更好的方法来迭代两个列表以查找项目之间的关系?,python,performance,optimization,Python,Performance,Optimization,我模拟ip列表和子网目录作为输入: # ip address list ip_list = [ '192.168.1.151', '192.168.10.191', '192.168.6.127', '192.168.2.227', '192.168.2.5', '192.168.3.237', '192.168.6.188', '192.168.7.209', '192.168.9.10', # Edited: add some /28, /16 case '192.168.12.39',

我模拟ip列表和子网目录作为输入:

# ip address list
ip_list = [
'192.168.1.151', '192.168.10.191', '192.168.6.127', 
'192.168.2.227', '192.168.2.5', '192.168.3.237', 
'192.168.6.188', '192.168.7.209', '192.168.9.10',
# Edited: add some /28, /16 case
'192.168.12.39', '192.168.12.58', '10.63.11.1', '10.63.102.69',
]

# subnet dict
netsets = {
'192.168.1.0/24': 'subnet-A',     # {subnet: subnet's name} 
'192.168.10.0/24': 'subnet-B', 
'192.168.2.0/24': 'subnet-C', 
'192.168.3.0/24': 'subnet-C',
'192.168.6.0/24': 'subnet-D', 
'192.168.7.0/24': 'subnet-D', 
'192.168.9.0/24': 'subnet-E',
# Edited: add some /28, /16 case
'192.168.12.32/28': 'subnet-F',
'192.168.12.48/28': 'subnet-G',
'10.63.0.0/16': 'subnet-I',
}
# ip address list ip_list = 
[ '192.168.1.151', '192.168.10.191', '192.168.6.127',  '192.168.2.227', '192.168.2.5', '192.168.3.237', 
'192.168.6.188', '192.168.7.209', '192.168.9.10' ]

# subnet dict 
netsets = { '192.168.1.0/24': 'subnet-A', # {subnet: subnet's name}
'192.168.10.0/24': 'subnet-B',  
'192.168.2.0/24':'subnet-C',  
'192.168.3.0/24': 'subnet-C', 
'192.168.6.0/24': 'subnet-D',  
'192.168.7.0/24': 'subnet-D',  
'192.168.9.0/24':'subnet-E', } 
然后,
ip\u列表中的每个ip地址都需要找到子网的名称

我们假设每个ip地址都可以在
netsets
中找到相应的子网

输出如下:

192.168.1.151   subnet-A
192.168.10.191  subnet-B
192.168.6.127   subnet-D
192.168.2.227   subnet-C
192.168.2.5     subnet-C
192.168.3.237   subnet-C
192.168.6.188   subnet-D
192.168.7.209   subnet-D
192.168.9.10    subnet-E
# add some /28, /16 case
192.168.12.39   subnet-F
192.168.12.58   subnet-G
10.63.11.1      subnet-I
10.63.102.69    subnet-I
192.168.1.151   subnet-A
192.168.10.191  subnet-B
192.168.6.127   subnet-D
192.168.2.227   subnet-C
192.168.2.5     subnet-C
192.168.3.237   subnet-C
192.168.6.188   subnet-D
192.168.7.209   subnet-D
192.168.9.10    subnet-E
我用来计算CIDR,下面是我的代码:

from netaddr import IPAddress, IPNetwork

def netaddr_test(ips, netsets):
    for ip in ips:
        for subnet, name in netsets.iteritems():
            if IPAddress(ip) in IPNetwork(subnet):
                print ip, '\t',  name
                break

netaddr_test(ip_list, netsets)
但是这段代码太慢,迭代太多。时间的复杂性为O(n**2)

一旦我们有成千上万的ip需要迭代,这段代码就花费了太多的时间

有没有更好的办法来解决这个问题

# ip address list
ip_list = [
'192.168.1.151', '192.168.10.191', '192.168.6.127',
'192.168.2.227', '192.168.2.5', '192.168.3.237',
'192.168.6.188', '192.168.7.209', '192.168.9.10'
]

# subnet dict
netsets = {
'192.168.1.0/24': 'subnet-A',     # {subnet: subnet's name} 
'192.168.10.0/24': 'subnet-B',
'192.168.2.0/24': 'subnet-C',
'192.168.3.0/24': 'subnet-C',
'192.168.6.0/24': 'subnet-D',
'192.168.7.0/24': 'subnet-D',
'192.168.9.0/24': 'subnet-E',
}
new_netsets = {}
for k,v in netsets.items():
   new_netsets['.'.join(k.split('.')[:3])] = v

for IP in ip_list:
   newIP = '.'.join(IP.split('.')[:3])
   print IP, new_netsets[newIP]

希望这有帮助。

我建议避免在for循环中创建新实例。这不会降低复杂性(会增加复杂性),但会加快NetAddress\u测试的速度,尤其是在多次调用时。例如:

def _init(ips, netsets):
    """Initialize all objects"""
    new_ips = []
    new_subs = {}
    for ip in ips:
         new_ips.append(IPAddress(ip))

    for subnet, info in netsets.iteritems():

        new_subs[subnet] = {'name': info, 'subnet': IPNetwork(subnet)}

    return new_ips, new_subs

def netaddr_test(ips, netsets):
    for ip in ips:
        for stringnet, info in netsets.iteritems():
            if ip in info['subnet']:
                print ip, '\t',  info['name']
                break

ni, ns = _init(ip_list, netsets)
netaddr_test(ni, ns)
更新:使用

ip_list = [
    '192.168.1.151', '192.168.10.191', '192.168.6.127', 
    '192.168.2.227', '192.168.2.5', '192.168.3.237', 
    '192.168.6.188', '192.168.7.209', '192.168.9.10'
] * 1000
结果:

# Original
$ time python /tmp/test.py > /dev/null

real    0m0.357s
user    0m0.345s
sys     0m0.012s

# Modified
$ time python /tmp/test2.py > /dev/null

real    0m0.126s
user    0m0.122s
sys     0m0.005s
现在,我从未使用过
netaddr
,因此我不确定它在内部如何处理子网。在您的情况下,您可以将子网视为一系列IP,每个IP都是一个
uint_32
,因此您可以将所有内容转换为整数:

 # IPs now are 
 ip_list_int = [3232235927, 3232238271, ...]

 netsets_expanded = {
     '192.168.1.0/24': {'name': 'subnet-A', 'start': 3232235776, 'end': 3232236031}
netaddr
可用于将数据转换为上述格式。到达后,您的
netaddr\u测试将变为(并且仅适用于整数比较):

def netaddr\u测试(ips、netset):
对于ip中的ip:
对于子网,netsets.iteritems()中的子信息:
如果ip>=subinfo['start']和ip
我模拟ip列表和子网目录作为输入:

# ip address list
ip_list = [
'192.168.1.151', '192.168.10.191', '192.168.6.127', 
'192.168.2.227', '192.168.2.5', '192.168.3.237', 
'192.168.6.188', '192.168.7.209', '192.168.9.10',
# Edited: add some /28, /16 case
'192.168.12.39', '192.168.12.58', '10.63.11.1', '10.63.102.69',
]

# subnet dict
netsets = {
'192.168.1.0/24': 'subnet-A',     # {subnet: subnet's name} 
'192.168.10.0/24': 'subnet-B', 
'192.168.2.0/24': 'subnet-C', 
'192.168.3.0/24': 'subnet-C',
'192.168.6.0/24': 'subnet-D', 
'192.168.7.0/24': 'subnet-D', 
'192.168.9.0/24': 'subnet-E',
# Edited: add some /28, /16 case
'192.168.12.32/28': 'subnet-F',
'192.168.12.48/28': 'subnet-G',
'10.63.0.0/16': 'subnet-I',
}
# ip address list ip_list = 
[ '192.168.1.151', '192.168.10.191', '192.168.6.127',  '192.168.2.227', '192.168.2.5', '192.168.3.237', 
'192.168.6.188', '192.168.7.209', '192.168.9.10' ]

# subnet dict 
netsets = { '192.168.1.0/24': 'subnet-A', # {subnet: subnet's name}
'192.168.10.0/24': 'subnet-B',  
'192.168.2.0/24':'subnet-C',  
'192.168.3.0/24': 'subnet-C', 
'192.168.6.0/24': 'subnet-D',  
'192.168.7.0/24': 'subnet-D',  
'192.168.9.0/24':'subnet-E', } 
然后ip_列表中的每个ip地址都需要找到 子网的名称

我们假设每个ip地址都可以在中找到相应的子网 netsets

输出如下:

192.168.1.151   subnet-A
192.168.10.191  subnet-B
192.168.6.127   subnet-D
192.168.2.227   subnet-C
192.168.2.5     subnet-C
192.168.3.237   subnet-C
192.168.6.188   subnet-D
192.168.7.209   subnet-D
192.168.9.10    subnet-E
# add some /28, /16 case
192.168.12.39   subnet-F
192.168.12.58   subnet-G
10.63.11.1      subnet-I
10.63.102.69    subnet-I
192.168.1.151   subnet-A
192.168.10.191  subnet-B
192.168.6.127   subnet-D
192.168.2.227   subnet-C
192.168.2.5     subnet-C
192.168.3.237   subnet-C
192.168.6.188   subnet-D
192.168.7.209   subnet-D
192.168.9.10    subnet-E
[……] 有没有更好的办法来解决这个问题

# ip address list
ip_list = [
'192.168.1.151', '192.168.10.191', '192.168.6.127',
'192.168.2.227', '192.168.2.5', '192.168.3.237',
'192.168.6.188', '192.168.7.209', '192.168.9.10'
]

# subnet dict
netsets = {
'192.168.1.0/24': 'subnet-A',     # {subnet: subnet's name} 
'192.168.10.0/24': 'subnet-B',
'192.168.2.0/24': 'subnet-C',
'192.168.3.0/24': 'subnet-C',
'192.168.6.0/24': 'subnet-D',
'192.168.7.0/24': 'subnet-D',
'192.168.9.0/24': 'subnet-E',
}
new_netsets = {}
for k,v in netsets.items():
   new_netsets['.'.join(k.split('.')[:3])] = v

for IP in ip_list:
   newIP = '.'.join(IP.split('.')[:3])
   print IP, new_netsets[newIP]
这是一个两行程序:

for ip_addr in ip_list:
    print "{0}\t{1}".format(ip_addr,netsets[".".join(ip_addr.split('.')[0:-1])+".0/24"])

我可以推荐使用特别优化的模块来快速搜索。因此,可以在O(m*logn)时间内解决该任务。例如:

   from intervaltree import Interval, IntervalTree
   from ipaddress import ip_network, ip_address

   # build nets tree
   netstree = IntervalTree(
                           Interval(
                                    ip_network(net).network_address, 
                                    ip_network(net).broadcast_address, 
                                    name
                                   ) 
                          for 
                          net, name 
                          in 
                          netsets.items()
                         )

   # Now you may check ip intervals     
   for i in ip_list:
       ip = ip_address(i)
       nets = netstree[ip]
       if nets:   # set is not empty
            netdata = list(nets)[0]
            print(netdata.data)
            # prints 'subnet-E'

在一般情况下,若要测试N个模板和M个值是否匹配,那个么除了O(N*M)之外,并没有什么能做得更好。但是如果你能重新制定任务,你就可以加快它

我的建议是对模板进行分组,这样您就可以拥有几个高级模板,如果某个IP与之匹配,那么您就可以进入最终模板。在您的示例中,这将是

grouped_netsets = {
    "192.168.0.0/16":  {
        '192.168.1.0/24': 'subnet-A',     # {subnet: subnet's name} 
        '192.168.10.0/24': 'subnet-B', 
        '192.168.2.0/24': 'subnet-C', 
        '192.168.3.0/24': 'subnet-C',
        '192.168.6.0/24': 'subnet-D', 
        '192.168.7.0/24': 'subnet-D', 
        '192.168.9.0/24': 'subnet-E',
        }
    }   

def netaddr_test(ips, grouped_netsets):
    for ip in ips:
        for group, netsets in grouped_netsets.iteritems():
            if IPAddress(ip) in IPNetwork(group):
                for subnet, name in netsets.iteritems():
                    if IPAddress(ip) in IPNetwork(subnet):
                        print(ip, '\t',  name)
                        break
所以,如果ip_列表包含任何不以192.168开头的内容,则只需一次检查即可将其删除


剩下的唯一问题是编写函数,用最佳配置对网络集进行分组。

假设子网彼此不重叠,您可以将子网转换为两个整数,即范围的开始和结束。这些数字将被添加到一个将被排序的列表中。在执行此操作时,我们需要构建一个字典,该字典可用于以后检索以范围开头的子网名称

def to_int(ip):
    parts = map(int, ip.split('.'))

    return parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3]

def build(netsets):
    ranges = []
    subnets = {}

    for net, name in netsets.iteritems():
        ip, size = net.split('/')
        start = to_int(ip)
        end = start | 0xffffffff >> int(size)
        ranges.extend([start, end])
        subnets[start] = name

    ranges.sort()
return ranges, subnets
使用前面的构建块,您可以使用以下代码轻松获得每个IP的子网:

ranges, subnets = build(netsets)
for ip in ip_list:
    print 'ip: {0}, subnet: {1}'.format(ip, find(ranges, subnets, ip))

构建字典和范围列表需要O(m log m)时间,而遍历IP列表需要O(n log m),其中m是子网的数量,n是IP的数量。解决方案适用于不同大小的不同子网,如果IP不属于任何子网,将打印
None

您是否只有/24个子网?如果是这样的话,你可以很容易地从IP地址中推断出子网。@alpha1554不,它可能是/16或其他,那么我认为你做得比O(n²)更好(我可能错了)。但是,您可以轻松地对该进程进行多线程处理。一些小的优化可能是预先计算
IPNetwork(subnet)
部分。如果您不关心内存使用情况,还可以构建一个字典,其中包含子网的所有IP作为密钥。但这是一种内存非常昂贵的方法。如果您有一个要搜索的子网层次结构,那么构建一个子网层次结构可能会有所帮助。从最高阶子网(/0)节点开始,然后根据匹配情况沿左分支或右分支走下去。这平均应该是O(logn)。此外,看起来对解决您的问题很有用。当然,只有当nestets键以“.0/24”+结尾时,它才起作用。它适用于不同的网络掩码/24、/16,甚至一些奇怪的-/21;+复杂性O(n*logn)谢谢。他的答案是克里斯托夫·特拉萨的答案。使用IP集。