# -*- coding: utf-8 -*- import re from abc import ABC from collections import namedtuple from dataclasses import dataclass, field from typing import Tuple, Iterator, Any, Set, Union from typing import Tuple, Iterator, Any, Set, List from aoc import BaseAssignment class Coordinate(namedtuple("Coordinate", ["x", "y"])): def distance(self, other: "Coordinate"): return abs(self.x - other.x) + abs(self.y - other.y) @dataclass class Sensor: coordinate: Coordinate nearest: Coordinate radius: int = None def __hash__(self): return hash(self.coordinate) def __post_init__(self): self.radius = self.coordinate.distance(self.nearest) def within_radius(self, coordinate: Coordinate) -> bool: distance = self.coordinate.distance(coordinate) return distance <= self.radius def x_coordinates_within_radius_at(self, y: int, map: "Map") -> Union[range, list]: coordinate = Coordinate(self.coordinate.x, y) if not self.within_radius(coordinate): return [] dy = abs(self.coordinate.y - y) left_over = self.radius - dy return range( max(self.coordinate.x - left_over, map.width[0]), min(self.coordinate.x + left_over + 1, map.width[-1]), ) @dataclass class Map: sensors: List[Sensor] = field(default_factory=list) beacons: Set[Coordinate] = field(default_factory=set) def __post_init__(self): all_coordinates = self.beacons | {sensor.coordinate for sensor in self.sensors} min_x = min(c.x for c in all_coordinates) max_x = max(c.x for c in all_coordinates) min_y = min(c.y for c in all_coordinates) max_y = max(c.y for c in all_coordinates) self.width = list(range(min_x, max_x + 1)) self.height = list(range(min_y, max_y + 1)) input_pattern = re.compile("x=(-?[-0-9]+), y=(-?[0-9]+)") class Assignment(BaseAssignment[int, Tuple[Sensor, Coordinate]], ABC): def parse_item(self, line: str) -> Tuple[Sensor, Coordinate]: match = input_pattern.findall(line) if len(match) != 2: raise RuntimeError() sensor_match, beacon_match = match beacon = Coordinate(int(beacon_match[0]), int(beacon_match[1])) sensor = Sensor(Coordinate(int(sensor_match[0]), int(sensor_match[1])), beacon) return sensor, beacon def create_map(self, input: Iterator[Tuple[Sensor, Coordinate]]) -> Map: sensors = list() beacons = set() for sensor, beacon in input: sensors.append(sensor) beacons.add(beacon) return Map(sensors, beacons) class AssignmentOne(Assignment): example_result = 26 example_kwargs = {"y": 10} # Wrong: 4062163 def run(self, input: Iterator[Tuple[Sensor, Coordinate]], y=2000000): map = self.create_map(input) x_coordinates = { x for sensor in map.sensors for x in sensor.x_coordinates_within_radius_at(y, map) } return len(x_coordinates) - len( [beacon.x for beacon in map.beacons if beacon.y == y] ) class AssignmentTwo(Assignment): pass