From b5a1498a51158290362c17f5dee25d90c35c285e Mon Sep 17 00:00:00 2001 From: Pavel Patsey Date: Sat, 18 Jan 2025 20:15:21 +0300 Subject: [PATCH] add parent_ip info to Node tuple --- cidr4_merger.py | 67 +++++++------ tests/test_cidr4_merger.py | 190 +++++++++++++++++++++---------------- 2 files changed, 146 insertions(+), 111 deletions(-) diff --git a/cidr4_merger.py b/cidr4_merger.py index bd6d4b4..4133171 100644 --- a/cidr4_merger.py +++ b/cidr4_merger.py @@ -1,7 +1,10 @@ import cProfile -from typing import Optional -Node = tuple[int, int, int] +Node = tuple[int, int, int, int] + + +class Cidr4MergerError(Exception): + pass def get_data(input_file): @@ -11,12 +14,13 @@ def get_data(input_file): def cidr4_to_node(cidr4: str) -> Node: - ip, mask_len = cidr4.strip().split("/") + ip_str, mask_len = cidr4.strip().split("/") mask_len = int(mask_len) - a, b, c, d = list(map(int, ip.split("."))) - ip_value = a * 256**3 + b * 256**2 + c * 256**1 + d * 256**0 + a, b, c, d = list(map(int, ip_str.split("."))) + ip = a * 256**3 + b * 256**2 + c * 256**1 + d * 256**0 added_ips_number = 0 - return ip_value, mask_len, added_ips_number + parent_ip = get_parent_ip(ip, mask_len) + return ip, mask_len, added_ips_number, parent_ip def sort_nodes(nodes: list[Node]) -> list[Node]: @@ -29,16 +33,14 @@ def get_net_addr(ip: int, mask_len: int) -> int: return net_addr -def get_parent_mask(ip: int, mask_len: int) -> Optional[int]: +def get_parent_ip(ip: int, mask_len: int) -> int: if mask_len == 0: - return None + raise Cidr4MergerError("The top of the tree has no parent!") return get_net_addr(ip, mask_len - 1) -def have_same_parent(ip_a, mask_len_a, ip_b, mask_len_b) -> bool: - return mask_len_a == mask_len_b and get_parent_mask( - ip_a, mask_len_a - ) == get_parent_mask(ip_b, mask_len_b) +def have_same_parent(mask_len_a, parent_ip_a, mask_len_b, parent_ip_b) -> bool: + return mask_len_a == mask_len_b and parent_ip_a == parent_ip_b def get_group_with_max_mask_len(nodes: list[Node]) -> list[Node]: @@ -46,16 +48,19 @@ def get_group_with_max_mask_len(nodes: list[Node]) -> list[Node]: return list(filter(lambda x: x[1] == max_mask_len, nodes)) -def get_parent(a: Node, b: Optional[Node] = None) -> Node: - ip_a, mask_len_a, added_ips_number_a = a - ip = get_parent_mask(ip_a, mask_len_a) - mask_len = mask_len_a - 1 - added_ips_number = added_ips_number_a + 2 ** (32 - mask_len_a) +def make_parent(a: Node, b: Node | None = None) -> Node: + ip_a, mask_len_a, added_ips_number_a, parent_ip_a = a if b: - ip_b, mask_len_b, added_ips_b = b - assert have_same_parent(ip_a, mask_len_a, ip_b, mask_len_b) + ip_b, mask_len_b, added_ips_b, parent_ip_b = b + if not have_same_parent(mask_len_a, parent_ip_a, mask_len_b, parent_ip_b): + raise Cidr4MergerError("Nodes must be neighbors!") added_ips_number = added_ips_number_a + added_ips_b - return ip, mask_len, added_ips_number + else: + added_ips_number = added_ips_number_a + 2 ** (32 - mask_len_a) + ip = parent_ip_a + mask_len = mask_len_a - 1 + parent_ip = get_parent_ip(ip, mask_len) + return ip, mask_len, added_ips_number, parent_ip def reduce_nodes(nodes: list[Node]) -> list[Node]: @@ -66,9 +71,9 @@ def reduce_nodes(nodes: list[Node]) -> list[Node]: i = 0 while i < len(group) - 1: a, b = group[i], group[i + 1] - ip_a, mask_len_a, _ = a - ip_b, mask_len_b, _ = b - if have_same_parent(ip_a, mask_len_a, ip_b, mask_len_b): + ip_a, mask_len_a, _, parent_ip_a = a + ip_b, mask_len_b, _, parent_ip_b = b + if have_same_parent(mask_len_a, parent_ip_a, mask_len_b, parent_ip_b): neighbours.append((a, b)) i += 2 else: @@ -78,14 +83,14 @@ def reduce_nodes(nodes: list[Node]) -> list[Node]: loners.append(group[i]) if neighbours: - zipped = zip(neighbours, map(lambda x: get_parent(x[0], x[1]), neighbours)) + zipped = zip(neighbours, map(lambda x: make_parent(x[0], x[1]), neighbours)) min_zipped = min(zipped, key=lambda x: x[1][2]) (a, b), parent = min_zipped nodes.remove(a) nodes.remove(b) nodes.append(parent) elif loners: - zipped = zip(loners, map(get_parent, loners)) + zipped = zip(loners, map(make_parent, loners)) min_zipped = min(zipped, key=lambda x: x[1][2]) a, parent = min_zipped nodes.remove(a) @@ -102,10 +107,10 @@ def merge_nodes(nodes: list[Node], required_len: int) -> list[Node]: return nodes -def node_to_cidr4(ip_value, mask_len) -> str: - lst = [str(ip_value >> (i << 3) & 0xFF) for i in reversed(range(4))] - ip = ".".join(lst) - return f"{ip}/{mask_len}" +def make_cidr4(ip, mask_len) -> str: + lst = [str(ip >> (i << 3) & 0xFF) for i in reversed(range(4))] + ip_str = ".".join(lst) + return f"{ip_str}/{mask_len}" def answer(nodes: list[Node], required_len: int) -> tuple[list[str], int]: @@ -114,8 +119,8 @@ def answer(nodes: list[Node], required_len: int) -> tuple[list[str], int]: cidr4s = [] sum_added_ips = 0 - for ip_value, mask_len, added_ips in merged_nodes: - cidr4s.append(node_to_cidr4(ip_value, mask_len)) + for ip_value, mask_len, added_ips, _ in merged_nodes: + cidr4s.append(make_cidr4(ip_value, mask_len)) sum_added_ips += added_ips return cidr4s, sum_added_ips diff --git a/tests/test_cidr4_merger.py b/tests/test_cidr4_merger.py index 0d1e3d5..4035156 100644 --- a/tests/test_cidr4_merger.py +++ b/tests/test_cidr4_merger.py @@ -1,13 +1,16 @@ +import pytest + from cidr4_merger import ( + Cidr4MergerError, answer, cidr4_to_node, get_group_with_max_mask_len, get_net_addr, - get_parent, - get_parent_mask, + get_parent_ip, have_same_parent, + make_cidr4, + make_parent, merge_nodes, - node_to_cidr4, reduce_nodes, sort_nodes, ) @@ -35,13 +38,28 @@ ip_d = int(bin_d, 2) def test_cidr4_to_node(): - assert cidr4_to_node("4.78.139.0/24") == (72256256, 24, 0) - assert cidr4_to_node("0.0.0.0/32") == (0, 32, 0) + assert cidr4_to_node("4.78.139.0/24") == (72256256, 24, 0, 72256000) + assert cidr4_to_node("0.0.0.0/32") == (0, 32, 0, 0) + + assert cidr4_to_node("23.234.30.0/24") == (401219072, 24, 0, 401219072) + assert cidr4_to_node("172.217.0.0/19") == (2899902464, 19, 0, 2899902464) + assert cidr4_to_node("23.225.141.0/24") == (400657664, 24, 0, 400657408) + assert cidr4_to_node("31.13.94.0/23") == (520969728, 23, 0, 520969216) + + assert cidr4_to_node("0.0.0.0/2") == (0, 2, 0, 0) + assert cidr4_to_node("64.0.0.0/2") == (1073741824, 2, 0, 0) + assert cidr4_to_node("128.0.0.0/2") == (2147483648, 2, 0, 2147483648) + assert cidr4_to_node("192.0.0.0/2") == (3221225472, 2, 0, 2147483648) -def test_node_to_cidr4(): - assert node_to_cidr4(72256256, 24) == "4.78.139.0/24" - assert node_to_cidr4(0, 32) == "0.0.0.0/32" +def test_make_cidr4(): + assert make_cidr4(72256256, 24) == "4.78.139.0/24" + assert make_cidr4(0, 32) == "0.0.0.0/32" + + assert make_cidr4(401219072, 24) == "23.234.30.0/24" + assert make_cidr4(2899902464, 19) == "172.217.0.0/19" + assert make_cidr4(400657664, 24) == "23.225.141.0/24" + assert make_cidr4(520969728, 23) == "31.13.94.0/23" def test_get_net_addr(): @@ -52,15 +70,19 @@ def test_get_net_addr(): def test_get_parent_mask(): - assert get_parent_mask(ip_a, 6) == ip_c - assert get_parent_mask(ip_b, 6) == ip_c - assert get_parent_mask(0, 1) == 0 - assert get_parent_mask(0, 0) is None + assert get_parent_ip(ip_a, 6) == ip_c + assert get_parent_ip(ip_b, 6) == ip_c + assert get_parent_ip(0, 1) == 0 + + with pytest.raises(Exception) as exc_info: + get_parent_ip(0, 0) + assert str(exc_info.value) == "The top of the tree has no parent!" + assert exc_info.type is Cidr4MergerError def test_have_same_parent(): - assert have_same_parent(ip_a, 6, ip_b, 6) is True - assert have_same_parent(ip_a, 6, ip_b, 5) is False + assert have_same_parent(ip_c, 6, ip_c, 6) is True + assert have_same_parent(ip_c, 6, ip_c, 5) is False assert have_same_parent(ip_a, 6, ip_d, 6) is False assert have_same_parent(ip_a, 6, 0, 1) is False assert have_same_parent(ip_a, 6, 0, 0) is False @@ -69,123 +91,131 @@ def test_have_same_parent(): def test_sort_nodes(): assert sort_nodes( [ - (401219072, 24, 0), - (2899902464, 19, 0), - (400657664, 24, 0), - (520969728, 23, 0), + (401219072, 24, 0, 401219072), + (2899902464, 19, 0, 2899902464), + (400657664, 24, 0, 400657408), + (520969728, 23, 0, 520969216), ] ) == [ - (2899902464, 19, 0), - (520969728, 23, 0), - (400657664, 24, 0), - (401219072, 24, 0), + (2899902464, 19, 0, 2899902464), + (520969728, 23, 0, 520969216), + (400657664, 24, 0, 400657408), + (401219072, 24, 0, 401219072), ] def test_get_group_with_max_mask_len(): assert get_group_with_max_mask_len( [ - (2899902464, 19, 0), - (520969728, 23, 0), - (400657664, 24, 0), - (401219072, 24, 0), + (2899902464, 19, 0, 2899902464), + (520969728, 23, 0, 520969216), + (400657664, 24, 0, 400657408), + (401219072, 24, 0, 401219072), ] - ) == [(400657664, 24, 0), (401219072, 24, 0)] + ) == [(400657664, 24, 0, 400657408), (401219072, 24, 0, 401219072)] assert get_group_with_max_mask_len( [ - (401219072, 24, 0), - (2899902464, 19, 0), - (520969728, 23, 0), + (401219072, 24, 0, 401219072), + (2899902464, 19, 0, 2899902464), + (520969728, 23, 0, 520969216), ] - ) == [(401219072, 24, 0)] + ) == [(401219072, 24, 0, 401219072)] -def test_get_parent(): - assert get_parent((0, 2, 12), (1073741824, 2, 3)) == (0, 1, 15) - assert get_parent((2147483648, 2, 1), (3221225472, 2, 2)) == (2147483648, 1, 3) +def test_make_parent(): + assert make_parent((0, 2, 12, 0), (1073741824, 2, 3, 0)) == (0, 1, 15, 0) + assert make_parent( + (2147483648, 2, 1, 2147483648), (3221225472, 2, 2, 2147483648) + ) == (2147483648, 1, 3, 0) + + with pytest.raises(Exception) as exc_info: + make_parent((0, 2, 12, 0), (3221225472, 2, 2, 2147483648)) + assert str(exc_info.value) == "Nodes must be neighbors!" + assert exc_info.type is Cidr4MergerError def test_reduce_nodes(): assert reduce_nodes( [ - (0, 2, 12), - (1073741824, 2, 3), + (0, 2, 12, 0), + (1073741824, 2, 3, 0), ] ) == [ - (0, 1, 15), + (0, 1, 15, 0), ] assert reduce_nodes( [ - (0, 2, 12), - (1073741824, 2, 3), - (2147483648, 2, 1), - (3221225472, 2, 2), + (0, 2, 12, 0), + (1073741824, 2, 3, 0), + (2147483648, 2, 1, 2147483648), + (3221225472, 2, 2, 2147483648), ] ) == [ - (2147483648, 1, 3), - (0, 2, 12), - (1073741824, 2, 3), + (2147483648, 1, 3, 0), + (0, 2, 12, 0), + (1073741824, 2, 3, 0), ] assert reduce_nodes( [ - (0, 2, 12), - (2147483648, 1, 0), + (0, 2, 12, 0), + (2147483648, 1, 0, 0), ] ) == [ - (0, 1, 12 + 2**30), - (2147483648, 1, 0), + (0, 1, 12 + 2**30, 0), + (2147483648, 1, 0, 0), ] - assert reduce_nodes( - [ - (0, 1, 12 + 2**30), - (2147483648, 1, 0), - ] - ) == [ - (0, 0, 12 + 2**30), - ] + with pytest.raises(Exception) as exc_info: + reduce_nodes( + [ + (0, 1, 12 + 2**30, 0), + (2147483648, 1, 0, 0), + ] + ) + assert exc_info.type is Cidr4MergerError + assert str(exc_info.value) == "The top of the tree has no parent!" def test_merge_nodes(): assert merge_nodes( [ - (0, 2, 12), - (2147483648, 2, 1), - (3221225472, 2, 2), + (0, 2, 12, 0), + (2147483648, 2, 1, 2147483648), + (3221225472, 2, 2, 2147483648), ], 2, ) == [ - (2147483648, 1, 3), - (0, 2, 12), + (2147483648, 1, 3, 0), + (0, 2, 12, 0), ] - assert merge_nodes( - [ - (0, 2, 12), - (2147483648, 2, 1), - (3221225472, 2, 2), - ], - 1, - ) == [(0, 0, 15 + 2**30)] + with pytest.raises(Exception) as exc_info: + merge_nodes( + [ + (0, 2, 12, 0), + (2147483648, 2, 1, 2147483648), + (3221225472, 2, 2, 2147483648), + ], + 1, + ) + assert exc_info.type is Cidr4MergerError + assert str(exc_info.value) == "The top of the tree has no parent!" def test_answer(): assert answer( [ - (0, 2, 0), - (2147483648, 2, 0), - (3221225472, 2, 12), + (0, 2, 12, 0), + (2147483648, 2, 1, 2147483648), + (3221225472, 2, 2, 2147483648), ], 2, - ) == (["128.0.0.0/1", "0.0.0.0/2"], 12) + ) == (["128.0.0.0/1", "0.0.0.0/2"], 15) - assert answer( - [ - (0, 2, 0), - (2147483648, 2, 0), - ], - 1, - ) == (["0.0.0.0/0"], 2**31) + with pytest.raises(Exception) as exc_info: + answer([(0, 2, 0, 0), (2147483648, 2, 0, 2147483648)], 1) + assert exc_info.type is Cidr4MergerError + assert str(exc_info.value) == "The top of the tree has no parent!"