Taro Logo

IP to CIDR

Medium
Databricks logo
Databricks
44 views
Topics:
Bit Manipulation

Given a starting IP address ip and a number of IPs we need to cover n, return a representation of the address range in CIDR format.

A CIDR block is a string consisting of an IP address, followed by a slash, and then the number of leading bits in the address that are fixed. For example, "192.168.0.0/20" represents all IPs with the first 20 bits being 192.168. followed by 0..x, where x can be anything.

The IP address is represented as a 32-bit unsigned integer, so you need to convert the IP string to an integer first. You will return a list of CIDR blocks representing the requested range. You should return the most concise list possible.

Example 1:

Input: ip = "255.0.0.7", n = 10
Output: ["255.0.0.7/32","255.0.0.8/29","255.0.0.16/32"]
Explanation:
The initial IP address, when converted to an integer, is 4278190087.
We need to cover 10 addresses starting from 4278190087.
- 4278190087 is best represented as "255.0.0.7/32", because it is a single address.
- 4278190088 to 4278190095 is best represented as "255.0.0.8/29", because it covers 8 addresses in a single block.
- 4278190096 is best represented as "255.0.0.16/32", because it is a single address.
In total, we covered 1 + 8 + 1 = 10 addresses as required.

Constraints:

  • ip is a valid IPv4 address.
  • 1 <= n <= 1000

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What is the valid range for the IP address and the number of IPs 'n'? Are we dealing with IPv4 addresses only?
  2. Can 'n' be zero or negative? What should I return in those cases?
  3. How should the CIDR blocks be ordered in the output list? Is there a specific preference (e.g., by block size or IP address)?
  4. Are there any constraints on the maximum number of CIDR blocks in the output? We want to minimize the number of blocks, but is there also an upper bound?
  5. Can you provide a small example to illustrate the expected input and output format more clearly, especially for cases where multiple CIDR blocks are needed?

Brute Force Solution

Approach

The IP to CIDR problem is about finding the most compact way to represent a range of IP addresses. The brute force method involves systematically trying out different CIDR block sizes, starting from the smallest possible block and working our way up to larger ones, to see if we can perfectly cover the given range.

Here's how the algorithm would work step-by-step:

  1. Begin with the starting IP address.
  2. Try representing the range with a single IP address (a CIDR block of size /32).
  3. If the range has more than one IP address, try representing it with two IP addresses (a CIDR block of size /31).
  4. Continue increasing the CIDR block size (e.g., /30, /29, etc.), calculating the range of IP addresses that each block represents.
  5. For each CIDR block, check if it starts at the given starting IP address and if it's fully contained within the total range we need to cover.
  6. If a block is both correctly aligned and completely inside the range, add it to our list of CIDR blocks.
  7. Update the starting IP address to the next IP address that still needs to be covered in the range.
  8. Repeat the process of trying different CIDR block sizes until the entire initial IP address range is covered.
  9. The result will be a list of CIDR blocks that together represent the original IP range.

Code Implementation

def ip_to_cidr(ip_address):
    ip_integer = ip_to_integer(ip_address)
    results = []
    while ip_integer > 0:
        # Start from the largest possible CIDR block
        prefix_length = 32
        while prefix_length > 0:
            network_address = ip_integer & ((2**32 - 1) << (32 - prefix_length))

            #Check if the current IP can form a valid CIDR block
            if network_address == ip_integer:
                results.append(integer_to_ip(network_address) + '/' + str(prefix_length))
                ip_integer -= 2**(32 - prefix_length)
                break
            prefix_length -= 1
        if prefix_length == 0:
            results.append(integer_to_ip(ip_integer) + '/32')
            ip_integer -= 1
    return results

def ip_to_integer(ip_address):
    octets = ip_address.split('.')
    result = 0
    for octet in octets:
        result = (result << 8) + int(octet)
    return result

def integer_to_ip(ip_integer):
    octet_1 = (ip_integer >> 24) & 255
    octet_2 = (ip_integer >> 16) & 255
    octet_3 = (ip_integer >> 8) & 255
    octet_4 = ip_integer & 255
    return str(octet_1) + '.' + str(octet_2) + '.' + str(octet_3) + '.' + str(octet_4)

Big(O) Analysis

Time Complexity
O(n)The algorithm iterates through the IP address range at most once. In each iteration, it attempts to find the largest possible CIDR block starting from the current IP. The number of CIDR blocks in the final result is limited, and finding each block takes a fixed amount of time (determining the appropriate prefix length and updating the starting IP). Therefore, the time complexity is directly proportional to the number of IPs 'n' in the initial range, leading to O(n) time complexity.
Space Complexity
O(K)The algorithm constructs a list of CIDR blocks that represent the original IP range. The number of CIDR blocks in this list is not directly related to the magnitude of the IP range itself, but depends on the optimal grouping. In the worst case, it might require storing K CIDR blocks, where K is the number of blocks needed to represent the range. The amount of extra space needed grows linearly with the number of blocks K.

Optimal Solution

Approach

The core idea is to convert the IP address to its numerical representation and then find the largest possible CIDR block that starts at that address. We repeatedly find the largest CIDR, add it to the solution, and then move to the next address not covered by any CIDR block.

Here's how the algorithm would work step-by-step:

  1. Convert the given IP address into its equivalent 32-bit integer representation.
  2. While the number of addresses to cover is greater than zero, find the largest possible CIDR block that starts at the current integer representation.
  3. To find the largest CIDR, start with a block size of /32 (a single IP address) and check if a larger block (e.g., /31, /30, etc.) is still valid. A block is valid if the starting address of the block aligns with the block's size. This alignment check is done by checking if the integer representation of the IP address modulo the block size is zero.
  4. If a larger block is valid, increase the block size until you find the largest possible valid CIDR block.
  5. Add this largest CIDR block (represented as IP address/prefix length) to the solution.
  6. Update the current integer representation by adding the size of the CIDR block to it, and also reduce the number of addresses to cover by the block size.
  7. Repeat these steps until all addresses are covered.

Code Implementation

def ip_to_cidr(ip_address, number_of_ips):

    def ip_to_int(ip):
        octets = ip.split('.')
        result = 0
        for octet in octets:
            result = (result << 8) + int(octet)
        return result

    def int_to_ip(ip_int):
        octet1 = (ip_int >> 24) & 255
        octet2 = (ip_int >> 16) & 255
        octet3 = (ip_int >> 8) & 255
        octet4 = ip_int & 255
        return f'{octet1}.{octet2}.{octet3}.{octet4}'

    start_ip_int = ip_to_int(ip_address)
    cidr_blocks = []

    while number_of_ips > 0:
        # Find the largest possible CIDR block
        max_cidr = 32
        while max_cidr > 0:
            network_address = start_ip_int & (~((1 << (32 - max_cidr)) - 1))
            if network_address != start_ip_int:
                break

            number_of_ips_in_block = 1 << (32 - max_cidr)

            if number_of_ips_in_block > number_of_ips:
                max_cidr -= 1
                break

            max_cidr -= 1

        max_cidr += 1

        # Create the CIDR block string
        cidr_blocks.append(f'{int_to_ip(start_ip_int)}/{max_cidr}')
        
        number_of_ips_in_block = 1 << (32 - max_cidr)
        number_of_ips -= number_of_ips_in_block
        start_ip_int += number_of_ips_in_block

        # Prevent overflow past a 32 bit integer.
        if start_ip_int > 0xFFFFFFFF:
            break

    return cidr_blocks

Big(O) Analysis

Time Complexity
O(n log n)The outer loop iterates at most n times, where n is the number of IP addresses to cover. Inside the loop, the process of finding the largest valid CIDR block involves checking progressively larger blocks. This check can be done in at most 32 steps since CIDR prefixes range from /0 to /32, so it is effectively a constant time operation. Then, the while loop to check the alignment and find the next largest block will iterate at most log(n) times because we are essentially doubling the block size each time until we hit n. Thus the total operations approximate n * log(n) which results in O(n log n).
Space Complexity
O(K)The algorithm stores the resulting CIDR blocks in a list. The maximum number of CIDR blocks in the output is represented by K, where K is the number of iterations needed to cover all N addresses. In the worst case, each IP address could be a separate CIDR block (/32), resulting in K=N. Therefore, the space required for the solution list is proportional to the number of generated CIDR blocks, K. No other significant auxiliary data structures are used beyond the output list.

Edge Cases

CaseHow to Handle
ip is null or empty stringReturn an empty list or throw an IllegalArgumentException as invalid input.
n is zeroReturn an empty list as there are no IPs to cover.
n is negativeThrow an IllegalArgumentException as the number of IPs must be non-negative.
ip is not a valid IPv4 address formatThrow an IllegalArgumentException, indicating the IP address is malformed.
Integer overflow when converting IP to integer or calculating IP + n - 1Use long integer representation for IP address calculations to prevent overflow.
Resulting IP address (IP + n - 1) exceeds the maximum IPv4 addressHandle the overflow by either wrapping around to 0 or clamping the value to the maximum IPv4 address value.
n is very large, leading to a huge number of CIDR blocksEnsure efficient CIDR block calculation and list building to handle large 'n' without excessive memory usage.
IP and n such that the entire IPv4 range is coveredThe algorithm should correctly return a single CIDR block representing 0.0.0.0/0.