# -*- 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 from aoc import BaseAssignment class Coordinate(namedtuple("Coordinate", ["x", "y"])): def distance(self, other: "Coordinate"): return abs(other.x - self.x) + abs(other.y - self.y) @dataclass class Sensor: coordinate: Coordinate nearest: Coordinate def __hash__(self): return hash(self.coordinate) def __post_init__(self): self.radius = self.coordinate.distance(self.nearest) @dataclass class Map: sensors: Set[Sensor] = field(default_factory=set) 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 = set() beacons = set() for sensor, beacon in input: sensors.add(sensor) beacons.add(beacon) return Map(sensors, beacons) class AssignmentOne(Assignment): example_result = 26 example_kwargs = {"y": 10} def run(self, input: Iterator[Tuple[Sensor, Coordinate]], y=2000000): map = self.create_map(input) no_sensor = 0 for x in map.width: coordinate = Coordinate(x, y) for sensor in map.sensors: if sensor.nearest == coordinate: break distance = sensor.coordinate.distance(coordinate) if distance <= sensor.radius: no_sensor += 1 break return no_sensor class AssignmentTwo(Assignment): pass