""" Advent of Code 2021 - Day 05 Run with: python puzzles/day05.py inputs/day05.txt """ import pathlib import sys from typing import Dict, List, Tuple from collections import defaultdict from dataclasses import dataclass @dataclass class Location: x: int = 0 y: int = 0 def __post_init__(self): self.x = int(self.x) self.y = int(self.y) @dataclass class VolcanicVent: start: Location end: Location def __post_init__(self): if self.start.x == self.end.x: self.direction = "vertical" elif self.start.y == self.end.y: self.direction = "horizontal" else: self.direction = "diagnal" class Chart: chart: Dict = None vents: List = None def __init__(self, vents: List[VolcanicVent], diagnals: bool = True) -> None: self.vents = vents self._generate_chart(diagnals=diagnals) def _generate_chart(self, diagnals: bool = True) -> None: """ .......1.. ..1....1.. ..1....1.. .......1.. .112111211 .......... .......... .......... .......... 222111.... 0,9 -> 5,9 0,9 -> 2,9 """ self.chart = defaultdict(int) for vent in self.vents: if vent.direction != "diagnal": self._chart_line_horiz_vert(vent.start, vent.end) elif diagnals: self._chart_line_diagnal(vent.start, vent.end) def _chart_line_diagnal(self, start: Location, end: Location) -> None: if end.x > start.x: # direction: left x_range = range(start.x, end.x + 1) else: # direction: right x_range = range(start.x, end.x - 1, -1) # reverse range if end.y > start.y: # direction: down y_range = range(start.y, end.y + 1) else: # direction: up y_range = range(start.y, end.y - 1, -1) # reverse range for x, y in zip(x_range, y_range): self.chart[x, y] += 1 def _chart_line_horiz_vert(self, start: Location, end: Location) -> None: min_x = min(start.x, end.x) max_x = max(start.x, end.x) min_y = min(start.y, end.y) max_y = max(start.y, end.y) for x in range(min_x, max_x + 1): for y in range(min_y, max_y + 1): self.chart[x, y] += 1 def calculate_overlaps(self, max_depth: int = 1) -> int: return sum(depth_count > max_depth for depth_count in self.chart.values()) def part1(inputs: List[int]) -> int: c = Chart(inputs, diagnals=False) return c.calculate_overlaps() def part2(inputs: List[int]) -> int: c = Chart(inputs) return c.calculate_overlaps() def parse(inputs: str) -> List[int]: """Parse the input string""" results = [] for line in inputs.split("\n"): start_end = line.split(" -> ") results.append( VolcanicVent( start=Location(*start_end[0].split(",")), end=Location(*start_end[1].split(",")), ) ) return results def solve(path: str) -> Tuple[int, int]: """Solve the puzzle""" puzzle_input = parse(pathlib.Path(path).read_text().strip()) part1_result = part1(puzzle_input) part2_result = part2(puzzle_input) return part1_result, part2_result def main() -> None: for path in sys.argv[1:]: print(f"Input File: {path}") part1_result, part2_result = solve(path) print(f"Part 1 Result: {part1_result}") print(f"Part 2 Result: {part2_result}") if __name__ == "__main__": main()