#!/usr/bin/env python3 """ aggregate_to_16.py ----------------- Group a list of IPv4 addresses into /16 subnets and report the count per subnet. Usage: python3 aggregate_to_16.py [path/to/file.txt] If no file is given, the script reads from STDIN, so you can also do: cat ips.txt | python3 aggregate_to_16.py """ import sys from collections import Counter from ipaddress import IPv4Address, IPv4Network def ip_to_16net(ip_str: str) -> IPv4Network: """ Convert a single IPv4 string to its containing /16 network. Example: 10.3.45.78 → IPv4Network('10.3.0.0/16') """ # IPv4Network with strict=False treats the address as a host and # expands it to the requested prefix length. return IPv4Network(f"{IPv4Address(ip_str)}/16", strict=False) def main(): # ------------------------------------------------------------------ # 1️⃣ Get an iterator over the input lines (file or stdin) # ------------------------------------------------------------------ if len(sys.argv) > 1: source = open(sys.argv[1], "r") else: source = sys.stdin # ------------------------------------------------------------------ # 2️⃣ Build a Counter keyed by the /16 network string # ------------------------------------------------------------------ counter = Counter() for raw_line in source: line = raw_line.strip() if not line: # skip empty lines continue try: net = ip_to_16net(line) counter[str(net)] += 1 except ValueError: # If the line isn’t a valid IPv4 address we simply ignore it. # (You could also collect errors here if you wish.) continue # Close the file if we opened one if source is not sys.stdin: source.close() # ------------------------------------------------------------------ # 3️⃣ Print the summary sorted by network address # ------------------------------------------------------------------ for net in sorted(counter, key=lambda x: tuple(map(int, x.split('/')[0].split('.')))): #print(f"{net}\t{counter[net]} addresses") print(f"{net}") if __name__ == "__main__": main()