diff --git a/challenges/2022/14-regolithReservoir/py/__init__.py b/challenges/2022/14-regolithReservoir/py/__init__.py index 3e672eb..14e5e2a 100644 --- a/challenges/2022/14-regolithReservoir/py/__init__.py +++ b/challenges/2022/14-regolithReservoir/py/__init__.py @@ -35,14 +35,14 @@ def parse(instr: str) -> Tuple[Scan, Vector, Vector]: f = lambda x, y: x + y if dy < 0: f = lambda x, y: x - y - + for j in range(abs(dy) + 1): res[(points[i][0], f(points[i][1], j))] = State.WALL else: f = lambda x, y: x + y if dx < 0: f = lambda x, y: x - y - + for j in range(abs(dx) + 1): res[(f(points[i][0], j), points[i][1])] = State.WALL @@ -51,16 +51,11 @@ def parse(instr: str) -> Tuple[Scan, Vector, Vector]: max_x = max(keys, key=lambda x: x[0])[0] min_y = min(keys, key=lambda x: x[1])[1] max_y = max(keys, key=lambda x: x[1])[1] - + return res, (min_x, min_y), (max_x, max_y) -def is_out_of_bounds(v: Vector, min_v: Vector, max_v: Vector) -> bool: - return not ((min_v[0] <= v[0] <= max_v[0]) and (0 <= v[1] <= max_v[1])) - - class Challenge(BaseChallenge): - @staticmethod def one(instr: str) -> int: inp, min_pos, max_pos = parse(instr) @@ -68,7 +63,9 @@ class Challenge(BaseChallenge): cursor = (500, 0) grains = 0 - while not is_out_of_bounds(cursor, min_pos, max_pos): + while (min_pos[0] <= cursor[0] <= max_pos[0]) and ( + 0 <= cursor[1] <= max_pos[1] + ): x, y = cursor if inp.get((x, y + 1)) is None: @@ -91,7 +88,7 @@ class Challenge(BaseChallenge): @staticmethod def two(instr: str) -> int: - inp, min_pos, max_pos = parse(instr) + inp, _, max_pos = parse(instr) max_pos = (max_pos[0], max_pos[1] + 2) diff --git a/challenges/2022/15-beaconExclusionZone/README.md b/challenges/2022/15-beaconExclusionZone/README.md new file mode 100644 index 0000000..4f367ab --- /dev/null +++ b/challenges/2022/15-beaconExclusionZone/README.md @@ -0,0 +1 @@ +# [Day 15: Beacon Exclusion Zone](https://adventofcode.com/2022/day/15) diff --git a/challenges/2022/15-beaconExclusionZone/benchmark.json b/challenges/2022/15-beaconExclusionZone/benchmark.json new file mode 100644 index 0000000..f4a369f --- /dev/null +++ b/challenges/2022/15-beaconExclusionZone/benchmark.json @@ -0,0 +1,15 @@ +{ + "day": 15, + "dir": "challenges/2022/15-beaconExclusionZone", + "implementations": { + "Python": { + "part.1.avg": 2.488755464553833, + "part.1.max": 2.488755464553833, + "part.1.min": 2.488755464553833, + "part.2.avg": 295.4520, + "part.2.max": 295.4520, + "part.2.min": 295.4520 + } + }, + "numRuns": 1 +} \ No newline at end of file diff --git a/challenges/2022/15-beaconExclusionZone/info.json b/challenges/2022/15-beaconExclusionZone/info.json new file mode 100644 index 0000000..a7a9787 --- /dev/null +++ b/challenges/2022/15-beaconExclusionZone/info.json @@ -0,0 +1,17 @@ +{ + "inputFile": "input.txt", + "testCases": { + "one": [ + { + "input": "Sensor at x=2, y=18: closest beacon is at x=-2, y=15\nSensor at x=9, y=16: closest beacon is at x=10, y=16\nSensor at x=13, y=2: closest beacon is at x=15, y=3\nSensor at x=12, y=14: closest beacon is at x=10, y=16\nSensor at x=10, y=20: closest beacon is at x=10, y=16\nSensor at x=14, y=17: closest beacon is at x=10, y=16\nSensor at x=8, y=7: closest beacon is at x=2, y=10\nSensor at x=2, y=0: closest beacon is at x=2, y=10\nSensor at x=0, y=11: closest beacon is at x=2, y=10\nSensor at x=20, y=14: closest beacon is at x=25, y=17\nSensor at x=17, y=20: closest beacon is at x=21, y=22\nSensor at x=16, y=7: closest beacon is at x=15, y=3\nSensor at x=14, y=3: closest beacon is at x=15, y=3\nSensor at x=20, y=1: closest beacon is at x=15, y=3\n", + "expected": "26" + } + ], + "two": [ + { + "input": "Sensor at x=2, y=18: closest beacon is at x=-2, y=15\nSensor at x=9, y=16: closest beacon is at x=10, y=16\nSensor at x=13, y=2: closest beacon is at x=15, y=3\nSensor at x=12, y=14: closest beacon is at x=10, y=16\nSensor at x=10, y=20: closest beacon is at x=10, y=16\nSensor at x=14, y=17: closest beacon is at x=10, y=16\nSensor at x=8, y=7: closest beacon is at x=2, y=10\nSensor at x=2, y=0: closest beacon is at x=2, y=10\nSensor at x=0, y=11: closest beacon is at x=2, y=10\nSensor at x=20, y=14: closest beacon is at x=25, y=17\nSensor at x=17, y=20: closest beacon is at x=21, y=22\nSensor at x=16, y=7: closest beacon is at x=15, y=3\nSensor at x=14, y=3: closest beacon is at x=15, y=3\nSensor at x=20, y=1: closest beacon is at x=15, y=3\n", + "expected": "56000011" + } + ] + } +} \ No newline at end of file diff --git a/challenges/2022/15-beaconExclusionZone/py/__init__.py b/challenges/2022/15-beaconExclusionZone/py/__init__.py new file mode 100644 index 0000000..9365efc --- /dev/null +++ b/challenges/2022/15-beaconExclusionZone/py/__init__.py @@ -0,0 +1,143 @@ +from __future__ import annotations +import sys +from typing import * +from aocpy import BaseChallenge, Vector, foldl +import re +from dataclasses import dataclass +import portion as P + + +@dataclass +class Line: + m: int + c: int + + @staticmethod + def from_points(a: Vector, b: Vector) -> Line: + calc_m = (b.y - a.y) / (b.x - a.x) + calc_c = a.y - (calc_m * a.x) + return Line(int(calc_m), int(calc_c)) + + +parse_re = re.compile( + r"Sensor at x=(-?\d+), y=(-?\d+): closest beacon is at x=(-?\d+), y=(-?\d+)" +) + + +def parse(instr: str) -> List[Tuple[Vector, Vector]]: + res = [] + for line in instr.strip().splitlines(): + sensor_x, sensor_y, beacon_x, beacon_y = map(int, parse_re.match(line).groups()) + res.append( + ( + Vector(sensor_x, sensor_y), + Vector(beacon_x, beacon_y), + ) + ) + return res + + +def postprocess( + inp: List[Tuple[Vector, Vector]] +) -> Tuple[ + Dict[Vector, Tuple[Vector, Vector, Vector, Vector]], + Dict[Vector, Tuple[Line, Line, Line, Line]], +]: + # 0 top, 1 left, 2 right, 3 bottom + sensor_points: Dict[Vector, Tuple[Vector, Vector, Vector, Vector]] = {} + sensor_lines: Dict[Vector, Tuple[Line, Line, Line, Line]] = {} + + for (sensor, beacon) in inp: + mh = sensor.manhattan_distance(beacon) + points = ( + Vector(sensor.x, sensor.y - mh), + Vector(sensor.x - mh, sensor.y), + Vector(sensor.x + mh, sensor.y), + Vector(sensor.x, sensor.y + mh), + ) + sensor_points[sensor] = points + + lines = ( + Line.from_points(points[1], points[0]), + Line.from_points(points[3], points[1]), + Line.from_points(points[2], points[0]), + Line.from_points(points[2], points[3]), + ) + sensor_lines[sensor] = lines + + return sensor_points, sensor_lines + + +class Challenge(BaseChallenge): + @staticmethod + def one(instr: str) -> int: + inp = parse(instr) + sensor_points, sensor_lines = postprocess(inp) + + target_line = 10 if len(inp) == 14 else 2000000 + ranges: List[Tuple[int, int]] = [] + + for (sensor, _) in inp: + points = sensor_points[sensor] + # print("polygon(", (", ".join(map(str, (points[0], points[1], points[3], points[2])))), ")") + lines = sensor_lines[sensor] + if points[0].y <= target_line <= points[3].y: + if target_line == points[1].y: + ranges.append((points[2].x, points[1].x)) + elif points[0].y <= target_line < points[1].y: + ranges.append( + ( + int((target_line - lines[2].c) / lines[2].m), + int((target_line - lines[0].c) / lines[0].m), + ) + ) + else: + ranges.append( + ( + int((target_line - lines[3].c) / lines[3].m), + int((target_line - lines[1].c) / lines[1].m), + ) + ) + + s: Set[int] = set() + for x in ranges: + s = s.union(set(range(x[1], x[0] + 1))) + + for (_, beacon) in inp: + if beacon.y == target_line: + s.discard(beacon.x) + + return len(s) + + @staticmethod + def two(instr: str) -> int: + inp = parse(instr) + sensor_points, sensor_lines = postprocess(inp) + + max_coord = 20 if len(inp) == 14 else 4000000 + line_ranges: List[P.Interval] = [P.empty() for _ in range(max_coord + 1)] + + for (sensor, _) in inp: + p0, p1, p2, p3 = sensor_points[sensor] + l0, l1, l2, l3 = sensor_lines[sensor] + + for y in range(max(p0.y, 0), min(p3.y, max_coord) + 1): + a: int + b: int + if y == sensor.y: + a, b = p1.x, p2.x + elif p0.y <= y < p1.y: + a, b = int((y - l0.c) / l0.m), int((y - l2.c) / l2.m) + else: + a, b = int((y - l1.c) / l1.m), int((y - l3.c) / l3.m) + + line_ranges[y] = line_ranges[y] | P.closed( + max(a, 0), min(b, max_coord) + 1 + ) + + for y in range(len(line_ranges)): + res = line_ranges[y] + if not res.atomic: + return (4000000 * res[0].upper) + y + + return 0 diff --git a/challenges/2022/README.md b/challenges/2022/README.md index 2fc0b94..42d885e 100644 --- a/challenges/2022/README.md +++ b/challenges/2022/README.md @@ -28,4 +28,4 @@ Solutions to the [2022 Advent of Code](https://adventofcode.com/2022). | 12 - Hill Climbing Algorithm | ☆ ☆ | | | | 13 - Distress Signal | ☆ ☆ | | | | 14 - Regolith Reservoir | ★ ★ | [Python](14-regolithReservoir/py) | Simulating falling sand | - +| 15 - Beacon Exclusion Zone | ★ ★ | [Python](15-beaconExclusionZone/py) | Searching through a 4000000^2 size grid for a literal single empty spot | \ No newline at end of file diff --git a/challenges/2022/running-times.png b/challenges/2022/running-times.png index 22dfc7a..1b2dd60 100644 Binary files a/challenges/2022/running-times.png and b/challenges/2022/running-times.png differ diff --git a/lib/aocpy/__init__.py b/lib/aocpy/__init__.py index 3319f81..07063d4 100644 --- a/lib/aocpy/__init__.py +++ b/lib/aocpy/__init__.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import Any, TypeVar, Callable, Iterable @@ -26,4 +27,51 @@ def foldl(p: Callable[[U, T], U], i: Iterable[T], start: U) -> U: return res def foldr(p: Callable[[U, T], U], i: Iterable[T], start: U) -> U: - return foldl(p, reversed(i), start) \ No newline at end of file + return foldl(p, reversed(i), start) + + +class Vector: + x: int + y: int + + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + @staticmethod + def _is_vector_tuple(o: Any) -> bool: + return type(o) == tuple and len(o) == 2 and type(o[0]) == int and type(o[1]) == int + + def manhattan_distance(self, o: Vector) -> int: + return abs(self.x - o.x) + abs(self.y - o.y) + + def __add__(self, o: Any) -> Vector: + if Vector._is_vector_tuple(o): + return Vector(self.x + o[0], self.y + o[1]) + elif type(o) == Vector: + return Vector(self.x + o.x, self.y + o.y) + else: + raise ValueError(f"cannot add Vector and {type(o)}") + + def __sub__(self, o: Any) -> Vector: + if Vector._is_vector_tuple(o): + return Vector(self.x - o[0], self.y - o[1]) + elif type(o) == Vector: + return Vector(self.x - o.x, self.y - o.y) + else: + raise ValueError(f"cannot subtract Vector and {type(o)}") + + def __eq__(self, o: Any) -> bool: + if Vector._is_vector_tuple(o): + return self.x == o[0] and self.y == o[1] + elif type(o) == Vector: + return self.x == o.x and self.y == o.y + else: + raise ValueError(f"cannot equate Vector and {type(o)}") + + def __repr__(self) -> str: + # return f"Vector(x={self.x}, y={self.y})" + return f"({self.x}, {self.y})" + + def __hash__(self): + return hash(f"{self.x},{self.y}")