From c42deb14ddd3f42fa743c31005c9d6528c931cef Mon Sep 17 00:00:00 2001 From: pareronia <49491686+pareronia@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:02:40 +0100 Subject: [PATCH] AoC 2023 Day 19 Part 2 --- README.md | 2 +- src/main/python/AoC2023_19.py | 128 +++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5ce61233..a9a626b6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| python3 | [✓](src/main/python/AoC2023_01.py) | [✓](src/main/python/AoC2023_02.py) | [✓](src/main/python/AoC2023_03.py) | [✓](src/main/python/AoC2023_04.py) | [✓](src/main/python/AoC2023_05.py) | [✓](src/main/python/AoC2023_06.py) | [✓](src/main/python/AoC2023_07.py) | [✓](src/main/python/AoC2023_08.py) | [✓](src/main/python/AoC2023_09.py) | [✓](src/main/python/AoC2023_10.py) | [✓](src/main/python/AoC2023_11.py) | [✓](src/main/python/AoC2023_12.py) | [✓](src/main/python/AoC2023_13.py) | [✓](src/main/python/AoC2023_14.py) | [✓](src/main/python/AoC2023_15.py) | [✓](src/main/python/AoC2023_16.py) | [✓](src/main/python/AoC2023_17.py) | [✓](src/main/python/AoC2023_18.py) | | | | | | | | +| python3 | [✓](src/main/python/AoC2023_01.py) | [✓](src/main/python/AoC2023_02.py) | [✓](src/main/python/AoC2023_03.py) | [✓](src/main/python/AoC2023_04.py) | [✓](src/main/python/AoC2023_05.py) | [✓](src/main/python/AoC2023_06.py) | [✓](src/main/python/AoC2023_07.py) | [✓](src/main/python/AoC2023_08.py) | [✓](src/main/python/AoC2023_09.py) | [✓](src/main/python/AoC2023_10.py) | [✓](src/main/python/AoC2023_11.py) | [✓](src/main/python/AoC2023_12.py) | [✓](src/main/python/AoC2023_13.py) | [✓](src/main/python/AoC2023_14.py) | [✓](src/main/python/AoC2023_15.py) | [✓](src/main/python/AoC2023_16.py) | [✓](src/main/python/AoC2023_17.py) | [✓](src/main/python/AoC2023_18.py) | [✓](src/main/python/AoC2023_19.py) | | | | | | | | java | [✓](src/main/java/AoC2023_01.java) | [✓](src/main/java/AoC2023_02.java) | [✓](src/main/java/AoC2023_03.java) | [✓](src/main/java/AoC2023_04.java) | [✓](src/main/java/AoC2023_05.java) | [✓](src/main/java/AoC2023_06.java) | [✓](src/main/java/AoC2023_07.java) | [✓](src/main/java/AoC2023_08.java) | [✓](src/main/java/AoC2023_09.java) | [✓](src/main/java/AoC2023_10.java) | [✓](src/main/java/AoC2023_11.java) | [✓](src/main/java/AoC2023_12.java) | [✓](src/main/java/AoC2023_13.java) | [✓](src/main/java/AoC2023_14.java) | [✓](src/main/java/AoC2023_15.java) | [✓](src/main/java/AoC2023_16.java) | [✓](src/main/java/AoC2023_17.java) | [✓](src/main/java/AoC2023_18.java) | | | | | | | | | rust | [✓](src/main/rust/AoC2023_01/src/main.rs) | [✓](src/main/rust/AoC2023_02/src/main.rs) | [✓](src/main/rust/AoC2023_03/src/main.rs) | [✓](src/main/rust/AoC2023_04/src/main.rs) | | [✓](src/main/rust/AoC2023_06/src/main.rs) | [✓](src/main/rust/AoC2023_07/src/main.rs) | [✓](src/main/rust/AoC2023_08/src/main.rs) | [✓](src/main/rust/AoC2023_09/src/main.rs) | | | | | | [✓](src/main/rust/AoC2023_15/src/main.rs) | [✓](src/main/rust/AoC2023_16/src/main.rs) | [✓](src/main/rust/AoC2023_17/src/main.rs) | [✓](src/main/rust/AoC2023_18/src/main.rs) | | | | | | | | diff --git a/src/main/python/AoC2023_19.py b/src/main/python/AoC2023_19.py index 1dc7f8d1..6613714f 100644 --- a/src/main/python/AoC2023_19.py +++ b/src/main/python/AoC2023_19.py @@ -3,7 +3,11 @@ # Advent of Code 2023 Day 19 # +from __future__ import annotations + import sys +from collections import defaultdict +from math import prod from typing import NamedTuple from aoc import my_aocd @@ -33,6 +37,9 @@ """ +Range = tuple[int, int] + + class Part(NamedTuple): x: int m: int @@ -43,24 +50,63 @@ def score(self) -> int: return sum([self.x, self.m, self.a, self.s]) +class PartRange(NamedTuple): + x: Range + m: Range + a: Range + s: Range + + def copy_with(self, prop: str, value: Range) -> PartRange: + return PartRange( + value if prop == "x" else self.x, + value if prop == "m" else self.m, + value if prop == "a" else self.a, + value if prop == "s" else self.s, + ) + + def score(self) -> int: + return prod(r[1] - r[0] + 1 for r in [self.x, self.m, self.a, self.s]) + + class Rule(NamedTuple): + operand1: str operation: str + operand2: int result: str def eval(self, part: Part) -> str | None: - if self.operation.startswith("x"): - x = part.x # noqa[F841] - elif self.operation.startswith("m"): - m = part.m # noqa[F841] - elif self.operation.startswith("a"): - a = part.a # noqa[F841] - elif self.operation.startswith("s"): - s = part.s # noqa[F841] - if eval(self.operation): # nosec + if ( + self.operation == "<" + and int.__lt__(getattr(part, self.operand1), self.operand2) + ) or ( + self.operation == ">" + and int.__gt__(getattr(part, self.operand1), self.operand2) + ): return self.result else: return None + def eval_range(self, r: PartRange) -> list[tuple[PartRange, str | None]]: + if self.operand2 == 0: + # TODO: janky!! + nr = r.copy_with(self.operand1, getattr(r, self.operand1)) + return [(nr, self.result)] + lo, hi = getattr(r, self.operand1) + if self.operation == "<": + match = (lo, self.operand2 - 1) + nomatch = (self.operand2, hi) + else: + match = (self.operand2 + 1, hi) + nomatch = (lo, self.operand2) + ans = list[tuple[PartRange, str | None]]() + if match[0] <= match[1]: + nr = r.copy_with(self.operand1, match) + ans.append((nr, self.result)) + if nomatch[0] <= nomatch[1]: + nr = r.copy_with(self.operand1, nomatch) + ans.append((nr, None)) + return ans + class Workflow(NamedTuple): name: str @@ -69,14 +115,35 @@ class Workflow(NamedTuple): def eval(self, part: Part) -> str: for rule in self.rules: res = rule.eval(part) - log( - f"eval workflow {self.name}, {rule.operation} on {part=}" - f"-> {res}" - ) + # log( + # f"eval [{self.name}: " + # f"{rule.operand1}{rule.operation}{rule.operand2}] on {part}" + # f"-> {res}" + # ) if res is not None: return res assert False + def eval_range(self, range: PartRange) -> list[tuple[PartRange, str]]: + ans = list[tuple[PartRange, str]]() + ranges = [range] + for rule in self.rules: + new_ranges = list[PartRange]() + for r in ranges: + ress = rule.eval_range(r) + for res in ress: + if res[1] is not None: + ans.append((res[0], res[1])) + else: + new_ranges.append(res[0]) + log( + f"eval [{self.name}: " + f"{rule.operand1}{rule.operation}{rule.operand2}]" + f" on {r} -> {ress}" + ) + ranges = new_ranges + return ans + class System(NamedTuple): workflows: dict[str, Workflow] @@ -100,10 +167,15 @@ def parse_input(self, input_data: InputData) -> Input: for r in rr: if ":" in r: op, res = r.split(":") + operand1 = op[0] + operation = op[1] + operand2 = int(op[2:]) else: - op = "True" + operand1 = "x" + operation = ">" + operand2 = 0 res = r - rules.append(Rule(op, res)) + rules.append(Rule(operand1, operation, operand2, res)) workflows[name] = Workflow(name, rules) parts = list[Part]() for p in pp: @@ -119,7 +191,6 @@ def parse_input(self, input_data: InputData) -> Input: return System(workflows, parts) def part_1(self, system: Input) -> Output1: - log(system) ans = 0 for part in system.parts: w = system.workflows["in"] @@ -135,13 +206,32 @@ def part_1(self, system: Input) -> Output1: continue return ans - def part_2(self, input: Input) -> Output2: - return 0 + def part_2(self, system: Input) -> Output2: + ans = 0 + prs = [(PartRange((1, 4000), (1, 4000), (1, 4000), (1, 4000)), "in")] + d = defaultdict[str, int](int) + d["in"] = 4000**4 + log(d) + while prs: + new_prs = list[tuple[PartRange, str]]() + for pr in prs: + for res in system.workflows[pr[1]].eval_range(pr[0]): + d[res[1]] += res[0].score() + if res[1] == "R": + continue + elif res[1] == "A": + ans += res[0].score() + continue + else: + new_prs.append((res[0], res[1])) + log(d) + prs = new_prs + return ans @aoc_samples( ( ("part_1", TEST, 19114), - # ("part_2", TEST, "TODO"), + ("part_2", TEST, 167409079868000), ) ) def samples(self) -> None: