2012-04-13 11 views
7

Supongamos que tengo una lista de rangos de IP (último término sólo) que pueden o no pueden superponerse:Consolidar IPs en rangos de pitón

('1.1.1.1-7', '2.2.2.2-10', '3.3.3.3-3.3.3.3', '1.1.1.4-25', '2.2.2.4-6') 

Estoy buscando una manera de identificar cualquier rangos que se solapan y consolidar ellos en rangos individuales.

('1.1.1.1-25', '2.2.2.2-10', '3.3.3.3-3') 

pensamiento actual para el algoritmo es ampliar todos los rangos en una lista de todas las direcciones IP, eliminar duplicados, ordenar y consolidar cualquier IP consecutivos.

¿Alguna otra sugerencia de algoritmo de python?

+0

Ordenación y luego la consolidación de sonidos como una solución bastante buena para mí. O (nlg (n)). No estoy seguro de si esto puede mejorarse. –

+0

@ColinD Estoy seguro de que le gustaría evitar ir de rangos a listas – agf

Respuesta

5

Aquí está mi versión, como un módulo. Mi algoritmo es idéntico al que menciona lunixbochs en su respuesta, y la conversión de cadena de rango a enteros y viceversa está muy bien modularizada.

import socket, struct 

def ip2long(ip): 
    packed = socket.inet_aton(ip) 
    return struct.unpack("!L", packed)[0] 

def long2ip(n): 
    unpacked = struct.pack('!L', n) 
    return socket.inet_ntoa(unpacked) 

def expandrange(rng): 
    # expand '1.1.1.1-7' to ['1.1.1.1', '1.1.1.7'] 
    start, end = [ip.split('.') for ip in rng.split('-')] 
    return map('.'.join, (start, start[:len(start) - len(end)] + end)) 

def compressrange((start, end)): 
    # compress ['1.1.1.1', '1.1.1.7'] to '1.1.1.1-7' 
    start, end = start.split('.'), end.split('.') 
    return '-'.join(map('.'.join, 
      (start, end[next((i for i in range(4) if start[i] != end[i]), 3):]))) 

def strings_to_ints(ranges): 
    # turn range strings into list of lists of ints 
    return [map(ip2long, rng) for rng in map(expandrange, ranges)] 

def ints_to_strings(ranges): 
    # turn lists of lists of ints into range strings 
    return [compressrange(map(long2ip, rng)) for rng in ranges] 

def consolodate(ranges): 
    # join overlapping ranges in a sorted iterable 
    iranges = iter(ranges) 
    startmin, startmax = next(iranges) 
    for endmin, endmax in iranges: 
     # leave out the '+ 1' if you want to join overlapping ranges 
     # but not consecutive ranges. 
     if endmin <= (startmax + 1): 
      startmax = max(startmax, endmax) 
     else: 
      yield startmin, startmax 
      startmin, startmax = endmin, endmax 
    yield startmin, startmax 

def convert_consolodate(ranges): 
    # convert a list of possibly overlapping ip range strings 
    # to a sorted, consolodated list of non-overlapping ip range strings 
    return list(ints_to_strings(consolodate(sorted(strings_to_ints(ranges))))) 

if __name__ == '__main__': 
    ranges = ('1.1.1.1-7', 
       '2.2.2.2-10', 
       '3.3.3.3-3.3.3.3', 
       '1.1.1.4-25', 
       '2.2.2.4-6') 
    print convert_consolodate(ranges) 
    # prints ['1.1.1.1-25', '2.2.2.2-10', '3.3.3.3-3'] 
+0

Creo que 'ranges = ('1.1.1.1-7 ',' 1.1.1.8-10 ') 'debe combinarse con' [' 1.1.1.1-10 '] '. Ahora la salida es '['1.1.1.1-7', '1.1.1.8-10']' – ovgolovin

+0

@ovgolovin Lo tomé literalmente. Esos son rangos "consecutivos", no rangos "superpuestos". – agf

+0

@ovgolovin Realmente, tienes razón, casi seguro que quiere unir rangos consecutivos. Cambié mi código para trabajar en enteros en lugar de listas de enteros, por lo que puede manejar fácilmente rangos consecutivos. – agf

1

Una vez me enfrenté al mismo problema. La única diferencia era que tenía que mantener eficientemente los segmentos de línea en una lista. Fue para una simulación de Monte Carlo. Y los segmentos de línea recientemente generados aleatoriamente tuvieron que ser agregados a los segmentos de línea ordenados y fusionados existentes.

He adaptado el algoritmo a su problema usando la respuesta lunixbochs para convertir direcciones IP a números enteros.

Esta solución permite agregar un nuevo rango de IP a la lista existente de rangos fusionados (mientras que otras soluciones dependen de tener la lista de rangos para fusionar ordenada y no permiten agregar un rango nuevo ya combinado lista de rango). Se realiza en la función add_range utilizando el módulo bisect para encontrar el lugar donde insertar el nuevo rango de IP y luego eliminar los intervalos de IP redundantes e insertar el nuevo rango con los límites ajustados para que el nuevo rango abarque todos los rangos eliminados.

import socket 
import struct 
import bisect 


def ip2long(ip): 
    '''IP to integer''' 
    packed = socket.inet_aton(ip) 
    return struct.unpack("!L", packed)[0] 


def long2ip(n): 
    '''integer to IP''' 
    unpacked = struct.pack('!L', n) 
    return socket.inet_ntoa(unpacked) 


def get_ips(s): 
    '''Convert string IP interval to tuple with integer representations of boundary IPs 
'1.1.1.1-7' -> (a,b)''' 
    s1,s2 = s.split('-') 
    if s2.isdigit(): 
     s2 = s1[:-1] + s2 
    return (ip2long(s1),ip2long(s2)) 


def add_range(iv,R): 
    '''add new Range to already merged ranges inplace''' 
    left,right = get_ips(R) 
    #left,right are left and right boundaries of the Range respectively 

    #If this is the very first Range just add it to the list 
    if not iv: 
     iv.append((left,right)) 
     return 

    #Searching the first interval with left_boundary < left range side 
    p = bisect.bisect_right(iv, (left,right)) #place after the needed interval 
    p -= 1 #calculating the number of interval basing on the position where the insertion is needed 


    #Interval: |----X----| (delete)  
    #Range: <--<--|----------| (extend) 
    #Detect if the left Range side is inside the found interval 
    if p >=0: #if p==-1 then there was no interval found 
     if iv[p][1]>= right: 
      #Detect if the Range is completely inside the interval 
      return #drop the Range; I think it will be a very common case 

     if iv[p][1] >= left-1: 
      left = iv[p][0] #extending the left Range interval 
      del iv[p] #deleting the interval from the interval list 
      p -= 1 #correcting index to keep the invariant 


    #Intervals: |----X----| |---X---| (delete)  
    #Range: |-----------------------------|   
    #Deleting all the intervals which are inside the Range interval 
    while True: 
     p += 1 
     if p >= len(iv) or iv[p][0] >= right or iv[p][1] > right: 
      'Stopping searching for the intervals which is inside the Range interval' 
      #there are no more intervals or 
      #the interval is to the right of the right Range side 
      # it's the next case (right Range side is inside the interval) 
      break 
     del iv[p] #delete the now redundant interval from the interval list 
     p -= 1 #correcting index to keep the invariant 


    #Interval: |--------X--------| (delete)  
    #Range: |-----------|-->--> (extend)  
    #Working the case when the right Range side is inside the interval 
    if p < len(iv) and iv[p][0] <= right-1: 
     #there is no condition for right interval side since 
     #this case would have already been worked in the previous block 
     right = iv[p][1] #extending the right Range side 
     del iv[p] #delete the now redundant interval from the interval list 
     #No p -= 1, so that p is no pointing to the beginning of the next interval 
     #which is the position of insertion 


    #Inserting the new interval to the list 
    iv.insert(p, (left,right)) 


def merge_ranges(ranges): 
    '''Merge the ranges''' 
    iv = [] 
    for R in ranges: 
     add_range(iv,R) 
    return ['-'.join((long2ip(left),long2ip(right))) for left,right in iv] 

ranges = ('1.1.1.1-7', '2.2.2.2-10', '3.3.3.3-3.3.3.3', '1.1.1.4-25', '2.2.2.4-6') 
print(merge_ranges(ranges)) 

Salida:

['1.1.1.1-1.1.1.25', '2.2.2.2-2.2.2.10', '3.3.3.3-3.3.3.3'] 

Esto fue muy divertido para mí al código! Gracias por eso :)

+0

Me pregunto si es posible reescribir este algoritmo que extiende suavemente los rangos existentes en la lista (tendrían que ser 'list's not' tuple's) en lugar de eliminar los rangos existentes y agregar el nuevo todo abarcativo al final con 'iv.insert (p, (left, right))'. Acabo de utilizar ['blist'] (http://stutzbachenterprises.com/blist/blist.html#blist) para evitar O (n) en las eliminaciones e inserciones en la' lista' tradicional. Pero me pregunto si alguien podría llegar a una buena solución algorítmica que evitaría * eliminaciones e inserciones * redundantes. – ovgolovin

+0

No creo que pueda evitar inserciones/eliminaciones sin datos ordenados previamente, lo que negaría la necesidad de su método. Parece que tu algoritmo es O (nlogn), suponiendo que tus inerciones y eliminaciones no son peores que O (logn) que es lo mismo que el algoritmo mencionado por lunixbochs y yo uso, excepto que el tu hace cada O (logn) paso manualmente mientras que el nuestro depende de un género y luego combina los rangos en tiempo lineal. Probablemente, nuestra versión será más rápida en CPython porque la clasificación se hace en C. – agf

+0

@agf Este algoritmo es solo una adaptación del viejo algoritmo hecho por mí para mantener los segmentos de línea generados aleatoriamente. Cada paso que generé algunos nuevos segmentos de línea y tuve que agregarlos a los existentes, por lo que necesitaba un algoritmo de fusión en línea eficaz. – ovgolovin

0

Unifica el formato de tus ips, transforma el rango en un par de entradas.

Ahora la tarea es mucho más simple: "consolidar" el rango entero. Creo que hay una gran cantidad de algoritmo eficiente existente para hacer eso, a continuación sólo mi ingenuo intento:

>>> orig_ranges = [(1,5), (7,12), (2,3), (13,13), (13,17)] # should result in (1,5), (7,12), (13,17) 
>>> temp_ranges = {}  
>>> for r in orig_ranges: 
     temp_ranges.setdefault(r[0], []).append('+') 
     temp_ranges.setdefault(r[1], []).append('-') 

>>> start_count = end_count = 0 
>>> start = None 
>>> for key in temp_ranges: 
     if start is None: 
      start = key 
     start_count += temp_ranges[key].count('+') 
     end_count += temp_ranges[key].count('-') 
     if start_count == end_count: 
      print start, key 
      start = None 
      start_count = end_count = 0 

1 5 
7 12 
13 17 

La idea general es la siguiente: después de poner rangos de uno a otro (en temp_ranges dict), podemos encontrar nuevos rangos compuestos simplemente contando los comienzos y finales de los rangos originales; una vez que obtuvimos la igualdad, encontramos un rango unificado.

1

Convierte tus rangos en pares de números. Estas funciones convertirán direcciones IP individuales desde y hacia valores enteros.

def ip2long(ip): 
    packed = socket.inet_aton(ip) 
    return struct.unpack("!L", packed)[0] 

def long2ip(n): 
    unpacked = struct.pack('!L', n) 
    return socket.inet_ntoa(unpacked) 

Ahora se puede ordenar/fusionar los bordes de cada rango como números, a continuación, convertir de nuevo a direcciones IP para obtener una representación agradable. Esta pregunta sobre merging time ranges tiene un buen algoritmo.


  1. analizar sintácticamente las cadenas de 1.1.1.1-1.1.1.2 y 1.1.1.1-2 en un par de números.Para este último formato, se puede hacer:

    x = '1.1.1.1-2' 
    first, add = x.split('-') 
    second = first.rsplit('.', 1)[0] + '.' + add 
    pair = ip2long(first), ip2long(second) 
    
  2. Combinar los rangos superpuestos utilizando comparaciones numéricas simples.

  3. convertir de nuevo a la representación de cadena (todavía asume último formato):

    first, second = pair 
    first = long2ip(first) + '-' + long2ip(second).rsplit('.', 1)[1] 
    
0

que éstos tenían por ahí en caso de que necesite em, utilizando socket/struct es probablemente mejor camino a seguir, aunque

def ip_str_to_int(address): 
    """Convert IP address in form X.X.X.X to an int. 

    >>> ip_str_to_int('74.125.229.64') 
    1249764672 

    """ 
    parts = address.split('.') 
    parts.reverse() 
    return sum(int(v) * 256 ** i for i, v in enumerate(parts)) 


def ip_int_to_str(address): 
    """Convert IP address int into the form X.X.X.X. 

    >>> ip_int_to_str(1249764672) 
    '74.125.229.64' 

    """ 
    parts = [(address & 255 << 8 * i) >> 8 * i for i in range(4)] 
    parts.reverse() 
    return '.'.join(str(x) for x in parts) 
+0

Esto no responde la pregunta, ya hay varios l versiones de esto en las otras respuestas, y tampoco es necesario para resolver el problema. Si desea publicar esto como respuesta, hay una pregunta en la que sería apropiado: [Conversión de cadena IP a entero, y hacia atrás en Python] (http://stackoverflow.com/questions/5619685/conversion-from- ip-string-to-integer-and-backward-in-python). – agf