Skip to content

Commit

Permalink
AoC 2023 Day 19 Part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
pareronia committed Dec 19, 2023
1 parent 335fc46 commit c42deb1
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<!-- @BEGIN:ImplementationsTable:2023@ -->
| | 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) | | | | | | | |
<!-- @END:ImplementationsTable:2023@ -->
Expand Down
128 changes: 109 additions & 19 deletions src/main/python/AoC2023_19.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -33,6 +37,9 @@
"""


Range = tuple[int, int]


class Part(NamedTuple):
x: int
m: int
Expand All @@ -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
Expand All @@ -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]
Expand All @@ -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:
Expand All @@ -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"]
Expand All @@ -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:
Expand Down

0 comments on commit c42deb1

Please sign in to comment.