2012-04-13 8 views
7

Si supponga Ho una lista di intervalli IP (ultimo termine solo) che possono o non possono sovrapporsi:Consolidare gli IP in intervalli in pitone

('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') 

Sto cercando un modo per identificare eventuali forcelle sovrapposte e consolidare in singole gamme.

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

pensiero attuale algoritmo è espandere tutti gli intervalli in un elenco di tutti gli IP, eliminare i duplicati, ordinare e consolidare eventuali IP consecutivi.

Altri suggerimenti sull'algoritmo di Python?

+0

Ordinamento e poi consolidare suona come una buona soluzione per me. O (NLG (n)). Non sono sicuro se questo può essere migliorato. –

+0

@ColinD Sono sicuro che vorrebbe evitare di passare dagli intervalli agli elenchi – agf

risposta

5

Ecco la mia versione, come modulo. Il mio algoritmo è identico a quello che lunixbochs menziona nella sua risposta, e la conversione da stringa di intervallo a numeri interi e posteriori è ben modularizzata.

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

Penso che 'ranges = ('1.1.1.1-7 ',' 1.1.1.8-10 ') 'devono essere uniti a' [' 1.1.1.1-10 '] '. Ora l'output è '['1.1.1.1-7', '1.1.1.8-10']' – ovgolovin

+0

@ovgolovin L'ho preso alla lettera. Quelli sono intervalli "consecutivi", non "sovrapposti". – agf

+0

@ovgolovin Davvero, hai ragione, quasi certamente vuole unirsi a intervalli consecutivi. Ho cambiato il mio codice per lavorare su numeri interi piuttosto che liste di interi in modo da poter gestire facilmente intervalli consecutivi. – agf

1

Una volta ho affrontato lo stesso problema. L'unica differenza era che dovevo mantenere in modo efficiente i segmenti di linea in un elenco. Era per una simulazione Monte-Carlo. E i nuovi segmenti di linea generati a caso dovevano essere aggiunti ai segmenti di linea ordinate e unite.

Ho adattato l'algoritmo al tuo problema utilizzando la risposta di lunixbochs per convertire gli IP in numeri interi.

Questa soluzione consente di aggiungere un nuovo intervallo IP all'elenco esistente di intervalli già uniti (mentre altre soluzioni si basano sull'avere ordinato l'elenco di intervalli da unire e non consentono l'aggiunta di un nuovo intervallo già unito elenco di intervalli). Viene eseguita nella funzione add_range utilizzando il modulo bisect per trovare il punto in cui inserire il nuovo intervallo IP e quindi eliminare gli intervalli IP ridondanti e inserire il nuovo intervallo con i limiti corretti in modo che il nuovo intervallo includa tutti gli intervalli eliminati.

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)) 

uscita:

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

Questo è stato molto divertente per me di codice! Grazie per quello :)

+0

Mi chiedo se sia possibile riscrivere questo algoritmo che estenda agevolmente gli intervalli esistenti nell'elenco (avrebbero bisogno di essere 'list's not' tuple's) invece di eliminare gli intervalli esistenti e aggiungendo il nuovo all-embracing alla fine con 'iv.insert (p, (left, right))'. Ho appena usato ['blist'] (http://stutzbachenterprises.com/blist/blist.html#blist) per evitare O (n) sulle eliminazioni e inserimenti nella tradizionale' lista'. Ma mi chiedo se qualcuno potrebbe trovare una buona soluzione algoritmica che eviti le cancellazioni e le ridondanze * ridondanti. – ovgolovin

+0

Non penso che sia possibile evitare inserimenti/eliminazioni senza dati preordinati, il che annullerebbe la necessità del metodo. Sembra che il tuo algoritmo sia O (nlogn), assumendo che le inerenze e le delezioni non siano peggiori di O (logn) che è lo stesso dell'algoritmo lunixbochs menzionato e che uso - tranne che il tuo fa ogni passo O (logn) manualmente, mentre il nostro dipende da un tipo e quindi unisce gli intervalli in tempo lineare. Probabilmente, la nostra versione sarà più veloce in CPython perché l'ordinamento è fatto in C. – agf

+0

@agf Questo algoritmo è solo un adattamento dal vecchio algoritmo fatto da me per mantenere i segmenti di linea generati casualmente. Ogni fase ho generato alcuni nuovi segmenti di linea e ho dovuto aggiungerli a quelli esistenti, quindi avevo bisogno di un efficace algoritmo di fusione in linea. – ovgolovin

0

Unifica il formato dei tuoi IP, trasforma il raggio in un paio di pollici.

Ora il compito è molto più semplice - "consolidare" l'intervallo di numeri interi. Credo che ci siano un sacco di algoritmo efficiente esistente per farlo, di seguito solo il mio tentativo ingenuo:

>>> 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 

L'idea generale è la seguente: dopo che abbiamo messo gamme uno su un altro (in temp_ranges dict), possiamo trovare nuovi campi composti semplicemente contando gli inizi e le terminazioni delle gamme originali; una volta ottenuta l'uguaglianza, abbiamo trovato un raggio unito.

1

Converti i tuoi intervalli in coppie di numeri. Queste funzioni convertiranno i singoli IP in e da valori interi.

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) 

Ora è possibile ordinare/unire i bordi di ogni serie come numeri, poi riconvertire gli IP per ottenere una bella rappresentazione. Questa domanda su merging time ranges ha un buon algoritmo.


  1. analizzare le stringhe di 1.1.1.1-1.1.1.2 e 1.1.1.1-2 in un paio di numeri.Per quest'ultima disposizione, si potrebbe fare:

    x = '1.1.1.1-2' 
    first, add = x.split('-') 
    second = first.rsplit('.', 1)[0] + '.' + add 
    pair = ip2long(first), ip2long(second) 
    
  2. Unire gli intervalli sovrapposti utilizzando semplici raffronti numerici.

  3. riconvertire rappresentazione di stringa (assume ancora Quest'ultimo formato):

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

ho avuto questi in giro in caso di necessità em, con presa/struct è modo probabilmente meglio andare anche se

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

Questo non risponde alla domanda, ci sono già sette l versioni di questo nelle altre risposte, e non è nemmeno necessario per risolvere il problema. Se vuoi pubblicare questo come una risposta, c'è una domanda dove sarebbe appropriato: [Conversione dalla stringa IP all'intero e all'indietro in Python] (http://stackoverflow.com/questions/5619685/conversion-from- ip-stringa a integer-and-backward-in-pitone). – agf

Problemi correlati