Implement IPv4 iterators and CIDR expansion
Company: OpenAI
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: English summary: This question evaluates proficiency with IP addressing, bitwise operations, and iterator implementation in code, testing competencies in binary arithmetic, CIDR range calculation, and memory- and time-aware iteration.
Part 1: Forward IPv4 Iterator Slice
Constraints
- start_ip is a valid IPv4 address.
- 0 <= n <= 100000
- Do not produce any address greater than 255.255.255.255.
Examples
Input: ('192.168.0.1', 3)
Expected Output: ['192.168.0.1', '192.168.0.2', '192.168.0.3']
Explanation: A basic forward walk from a normal starting address.
Input: ('192.168.0.255', 3)
Expected Output: ['192.168.0.255', '192.168.1.0', '192.168.1.1']
Explanation: This crosses an octet boundary.
Input: ('255.255.255.255', 3)
Expected Output: ['255.255.255.255']
Explanation: Edge case: the iterator starts at the final IPv4 address.
Input: ('0.0.0.0', 0)
Expected Output: []
Explanation: Edge case: requesting zero values should return an empty list.
Solution
def solution(start_ip, n):
def ip_to_int(ip):
a, b, c, d = map(int, ip.split('.'))
return (a << 24) | (b << 16) | (c << 8) | d
def int_to_ip(value):
return '.'.join(str((value >> shift) & 255) for shift in (24, 16, 8, 0))
if n <= 0:
return []
current = ip_to_int(start_ip)
limit = 0xFFFFFFFF
steps = min(n, limit - current + 1)
return [int_to_ip(current + i) for i in range(steps)]Time complexity: O(k), where k is the number of addresses returned. Space complexity: O(k).
Hints
- Convert the IPv4 string into a 32-bit integer so incrementing becomes simple.
- The largest possible IPv4 value is 2^32 - 1.
Part 2: Reverse IPv4 Iterator Slice
Constraints
- end_ip is a valid IPv4 address.
- 0 <= n <= 100000
- Do not produce any address smaller than 0.0.0.0.
Examples
Input: ('192.168.0.255', 3)
Expected Output: ['192.168.0.255', '192.168.0.254', '192.168.0.253']
Explanation: A normal reverse walk.
Input: ('10.0.0.0', 2)
Expected Output: ['10.0.0.0', '9.255.255.255']
Explanation: This crosses an octet boundary while moving backward.
Input: ('0.0.0.0', 5)
Expected Output: ['0.0.0.0']
Explanation: Edge case: the iterator starts at the minimum IPv4 address.
Input: ('1.0.0.0', 3)
Expected Output: ['1.0.0.0', '0.255.255.255', '0.255.255.254']
Explanation: This crosses the highest octet boundary.
Solution
def solution(end_ip, n):
def ip_to_int(ip):
a, b, c, d = map(int, ip.split('.'))
return (a << 24) | (b << 16) | (c << 8) | d
def int_to_ip(value):
return '.'.join(str((value >> shift) & 255) for shift in (24, 16, 8, 0))
if n <= 0:
return []
current = ip_to_int(end_ip)
steps = min(n, current + 1)
return [int_to_ip(current - i) for i in range(steps)]Time complexity: O(k), where k is the number of addresses returned. Space complexity: O(k).
Hints
- Treat the IPv4 address as an unsigned 32-bit integer and subtract 1 at each step.
- The smallest possible IPv4 value is 0.
Part 3: Expand a CIDR Block
Constraints
- cidr is valid and has a prefix length in the range 0 to 32.
- For this problem, the expanded range size will be at most 4096 addresses.
- Use bitwise masking to compute the network and end addresses.
Examples
Input: '192.168.1.0/30'
Expected Output: ('192.168.1.0', '192.168.1.3', ['192.168.1.0', '192.168.1.1', '192.168.1.2', '192.168.1.3'])
Explanation: A /30 block contains 4 addresses.
Input: '172.16.5.9/29'
Expected Output: ('172.16.5.8', '172.16.5.15', ['172.16.5.8', '172.16.5.9', '172.16.5.10', '172.16.5.11', '172.16.5.12', '172.16.5.13', '172.16.5.14', '172.16.5.15'])
Explanation: The input IP is not aligned; masking moves the start to 172.16.5.8.
Input: '10.0.0.5/32'
Expected Output: ('10.0.0.5', '10.0.0.5', ['10.0.0.5'])
Explanation: Edge case: a /32 contains exactly one address.
Input: '0.0.0.0/31'
Expected Output: ('0.0.0.0', '0.0.0.1', ['0.0.0.0', '0.0.0.1'])
Explanation: Edge case: a /31 contains two addresses.
Solution
def solution(cidr):
def ip_to_int(ip):
a, b, c, d = map(int, ip.split('.'))
return (a << 24) | (b << 16) | (c << 8) | d
def int_to_ip(value):
return '.'.join(str((value >> shift) & 255) for shift in (24, 16, 8, 0))
ip_text, prefix_text = cidr.split('/')
prefix = int(prefix_text)
ip_value = ip_to_int(ip_text)
if prefix == 0:
mask = 0
else:
mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
start = ip_value & mask
end = start | (0xFFFFFFFF ^ mask)
addresses = [int_to_ip(value) for value in range(start, end + 1)]
return int_to_ip(start), int_to_ip(end), addressesTime complexity: O(m), where m is the number of addresses in the CIDR range. Space complexity: O(m).
Hints
- To get the network address, keep the first prefix_len bits and zero out the rest.
- To get the highest address in the CIDR block, set all host bits to 1.
Part 4: Optimized CIDR Summary Without Full Expansion
Constraints
- cidr is valid and has a prefix length in the range 0 to 32.
- 0 <= k <= 1000
- The CIDR block may be as large as /0, so expanding the full range is not allowed.
Examples
Input: ('192.168.1.0/30', 2)
Expected Output: (4, '192.168.1.0', '192.168.1.3', ['192.168.1.0', '192.168.1.1'], ['192.168.1.2', '192.168.1.3'])
Explanation: A small block where only part of the range is sampled from each side.
Input: ('10.0.0.5/32', 3)
Expected Output: (1, '10.0.0.5', '10.0.0.5', ['10.0.0.5'], ['10.0.0.5'])
Explanation: Edge case: the block has only one address, so both samples contain the full block.
Input: ('0.0.0.0/0', 1)
Expected Output: (4294967296, '0.0.0.0', '255.255.255.255', ['0.0.0.0'], ['255.255.255.255'])
Explanation: Huge range: the answer must be produced without expanding all addresses.
Input: ('1.2.3.4/24', 0)
Expected Output: (256, '1.2.3.0', '1.2.3.255', [], [])
Explanation: Edge case: when k is zero, the sample lists are empty.
Solution
def solution(cidr, k):
def ip_to_int(ip):
a, b, c, d = map(int, ip.split('.'))
return (a << 24) | (b << 16) | (c << 8) | d
def int_to_ip(value):
return '.'.join(str((value >> shift) & 255) for shift in (24, 16, 8, 0))
ip_text, prefix_text = cidr.split('/')
prefix = int(prefix_text)
ip_value = ip_to_int(ip_text)
if prefix == 0:
mask = 0
else:
mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
start = ip_value & mask
count = 1 << (32 - prefix)
end = start + count - 1
if k <= 0:
first_k = []
last_k = []
else:
take = min(k, count)
first_k = [int_to_ip(start + i) for i in range(take)]
last_start = end - take + 1
last_k = [int_to_ip(last_start + i) for i in range(take)]
return count, int_to_ip(start), int_to_ip(end), first_k, last_kTime complexity: O(k). Space complexity: O(k).
Hints
- A CIDR block with prefix p contains 2^(32 - p) addresses.
- You can compute the first k and last k addresses directly from the integer start and end values.