2012-04-13 31 views
7

Giả sử tôi có một danh sách các dãy IP (hạn cuối cùng chỉ) có thể có hoặc không có thể chồng chéo:Củng cố IP vào dãy trong python

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

Tôi đang tìm kiếm một cách để xác định những dãy chồng chéo và củng cố chúng thành các dải đơn.

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

suy nghĩ hiện tại cho thuật toán là để mở rộng tất cả các dãy thành một danh sách tất cả các khu công nghiệp, loại bỏ các bản sao, sắp xếp và củng cố bất cứ IP liên tiếp.

Bất kỳ đề xuất thuật toán python-esque nào khác?

+0

Sắp xếp và sau đó củng cố âm thanh như một giải pháp khá tốt đối với tôi. O (nlg (n)). Không chắc chắn nếu điều này có thể được cải thiện. –

+0

@ColinD Tôi chắc chắn anh ấy muốn tránh đi từ phạm vi đến danh sách – agf

Trả lời

5

Đây là phiên bản của tôi, như một mô-đun. Thuật toán của tôi giống hệt với một lunixboch đề cập đến trong câu trả lời của anh ta, và việc chuyển đổi từ chuỗi số thành số nguyên và ngược lại được mô đun hóa một cách độc đáo.

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

Tôi nghĩ rằng 'range = ('1.1.1.1-7 ',' 1.1.1.8-10 ') 'nên được hợp nhất thành' [' 1.1.1.1-10 '] '. Bây giờ đầu ra là '[' 1.1.1.1-7 ',' 1.1.1.8-10 '] ' – ovgolovin

+0

@ovgolovin Tôi lấy anh ta theo nghĩa đen. Đó là phạm vi "liên tục", không phải phạm vi "chồng chéo". – agf

+0

@ovgolovin Thực sự, bạn nói đúng, anh ấy gần như chắc chắn muốn tham gia các dãy liên tiếp. Tôi đã thay đổi mã của tôi để làm việc trên các số nguyên chứ không phải là danh sách các số nguyên để nó có thể dễ dàng xử lý các dãy liên tiếp. – agf

1

Khi tôi gặp phải vấn đề tương tự. Sự khác biệt duy nhất là tôi phải giữ lại các đoạn thẳng trong danh sách một cách hiệu quả. Đó là một mô phỏng Monte-Carlo. Và các đoạn đường mới được tạo ngẫu nhiên phải được thêm vào các đoạn đường được sắp xếp và sắp xếp hiện tại.

Tôi đã điều chỉnh thuật toán cho vấn đề của bạn bằng cách sử dụng câu trả lời bằng cách lunixbochs để chuyển đổi IP thành số nguyên.

Giải pháp này cho phép thêm dải IP mới vào danh sách dải ô đã hợp nhất hiện có (trong khi các giải pháp khác dựa vào việc sắp xếp danh sách sắp xếp hợp nhất và không cho phép thêm dải ô mới đã được hợp nhất danh sách phạm vi). Nó được thực hiện theo chức năng add_range bằng cách sử dụng mô-đun bisect để tìm vị trí chèn dải IP mới và sau đó xóa các khoảng IP dự phòng và chèn phạm vi mới với các ranh giới được điều chỉnh để phạm vi mới bao trùm tất cả các dải ô đã xóa.

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

Output:

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

Đây là rất nhiều niềm vui cho tôi để viết mã! Cảm ơn bạn vì điều đó :)

+0

Tôi tự hỏi liệu có thể viết lại thuật toán này mà nó thuận lợi mở rộng phạm vi hiện có trong danh sách (chúng sẽ cần phải là 'list's không' tuple 's) thay vì xóa các phạm vi hiện có và thêm mới tất cả-ôm một ở cuối với' iv.insert (p, (trái, phải)) '. Tôi chỉ sử dụng ['blist'] (http://stutzbachenterprises.com/blist/blist.html#blist) để tránh O (n) xóa và chèn trong danh sách' truyền thống'. Nhưng tôi tự hỏi nếu ai đó có thể đưa ra một giải pháp thuật toán tốt mà có thể tránh được * xóa bỏ * thừa và chèn. – ovgolovin

+0

Tôi không nghĩ rằng bạn có thể tránh chèn/xóa mà không có dữ liệu được sắp xếp trước, điều này sẽ phủ nhận sự cần thiết cho phương pháp của bạn. Dường như thuật toán của bạn là O (nlogn), giả định rằng việc chèn và xóa của bạn không tệ hơn O (logn) giống như thuật toán lunixbochs đã đề cập và tôi sử dụng - ngoại trừ việc bạn thực hiện từng bước O (logn) theo cách thủ công, trong khi chúng ta phụ thuộc vào một sắp xếp và sau đó hợp nhất các dải trong thời gian tuyến tính. Có lẽ, phiên bản của chúng tôi sẽ nhanh hơn trong CPython vì sắp xếp được thực hiện trong C. – agf

+0

@agf Thuật toán này chỉ là một sự thích nghi từ thuật toán cũ được tôi tạo ra để giữ cho các đoạn đường được tạo ngẫu nhiên. Mỗi bước tôi tạo ra một vài phân đoạn dòng mới và phải thêm chúng vào các phân đoạn hiện có, vì vậy tôi cần một thuật toán kết hợp trực tuyến hiệu quả. – ovgolovin

0

Định dạng hợp nhất của ips của bạn, biến phạm vi thành một cặp int.

Bây giờ nhiệm vụ đơn giản hơn nhiều - "tổng hợp" phạm vi số nguyên. Tôi tin rằng có rất nhiều thuật toán hiệu quả hiện có để làm điều đó, dưới đây chỉ thử ngây thơ của tôi:

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

Ý tưởng chung là tiếp theo: sau khi chúng tôi đặt dãy một lên khác (trong temp_ranges dict), chúng ta có thể tìm thấy các phạm vi được tạo mới chỉ đơn giản bằng cách đếm số lần bắt đầu và kết thúc của phạm vi ban đầu; một khi chúng tôi đã bình đẳng, chúng tôi tìm thấy một phạm vi thống nhất.

1

Chuyển đổi phạm vi thành các cặp số. Các hàm này sẽ chuyển đổi các IP riêng lẻ thành và từ các giá trị số nguyên.

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) 

Bây giờ bạn có thể sắp xếp/hợp nhất các cạnh của mỗi phạm vi dưới dạng số, sau đó chuyển đổi lại thành IP để có biểu diễn đẹp. Câu hỏi này về merging time ranges có một thuật toán tốt đẹp.


  1. Phân tích chuỗi lại 1.1.1.1-1.1.1.21.1.1.1-2 thành một cặp số.Đối với định dạng sau, bạn có thể làm:

    x = '1.1.1.1-2' 
    first, add = x.split('-') 
    second = first.rsplit('.', 1)[0] + '.' + add 
    pair = ip2long(first), ip2long(second) 
    
  2. Hợp nhất các phạm vi chồng chéo bằng cách so sánh số đơn giản.

  3. Chuyển đổi trở lại chuỗi đại diện (vẫn giả định dạng sau):

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

tôi đã có những nằm xung quanh trong trường hợp bạn cần em, sử dụng ổ cắm/struct là cách có lẽ tốt hơn để đi mặc dù

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

Điều này không trả lời câu hỏi, đã có severa l phiên bản này trong các câu trả lời khác, và nó cũng không cần thiết để giải quyết vấn đề. Nếu bạn muốn đăng câu trả lời này, có một câu hỏi sẽ thích hợp: [Chuyển đổi từ chuỗi IP thành số nguyên và ngược lại trong Python] (http://stackoverflow.com/questions/5619685/conversion-from- ip-string-to-integer-and-backward-in-python). – agf

Các vấn đề liên quan