aoc2023/day19.py
2023-12-19 12:18:12 +01:00

171 lines
5.3 KiB
Python

from __future__ import annotations
from collections import defaultdict
from tools.aoc import AOCDay
from tools.math import mul
from typing import Any
class Part:
def __init__(self, part_str: str):
self.attr = defaultdict(int)
for desc in part_str[1:-1].split(","):
attr, value = desc.split("=")
self.attr[attr] = int(value)
def get_value(self) -> int:
return sum(self.attr.values())
class WorkflowItem:
def __init__(self, flow_str: str):
if ":" in flow_str:
check, self.check_target = flow_str.split(":")
self.check_attr = check[0]
self.check_comp = check[1]
self.check_value = int(check[2:])
else:
self.check_target = flow_str
self.check_comp = None
self.check_attr = None
self.check_value = None
def get_target(self, part: Part) -> str | None:
if self.check_attr is None:
return self.check_target
else:
match self.check_comp:
case "<":
result = part.attr[self.check_attr] < self.check_value
case ">":
result = part.attr[self.check_attr] > self.check_value
case _:
assert False, "unhandled comparator: %s" % self.check_comp
if result:
return self.check_target
def reverse(self) -> WorkflowItem:
if self.check_attr is None:
return self
else:
if self.check_comp == ">":
return WorkflowItem(f"{self.check_attr}<{self.check_value + 1}:{self.check_target}")
else:
return WorkflowItem(f"{self.check_attr}>{self.check_value - 1}:{self.check_target}")
class Workflow:
def __init__(self, workflow_str: str):
workflow_str = workflow_str[:-1]
self.name, workflow = workflow_str.split("{")
self.flow = [WorkflowItem(x) for x in workflow.split(",")]
self.source = None
def get_target(self, part: Part) -> str:
for flow in self.flow:
target = flow.get_target(part)
if target is not None:
return target
def has_target(self, target: str) -> bool:
return target in [x.check_target for x in self.flow]
def reached_workflow(self, target: str) -> list[list[WorkflowItem]]:
filter_lists = []
my_list = []
for item in self.flow:
if item.check_target != target:
my_list.append(item.reverse())
else:
if item.check_attr is None:
filter_lists.append(my_list)
else:
filter_lists.append(my_list + [item])
my_list.append(item.reverse())
if self.source is not None:
new_filter_lists = []
sub_lists = self.source.reached_workflow(self.name)
for sub_list in sub_lists:
for my_list in filter_lists:
new_filter_lists.append(sub_list + my_list)
filter_lists = new_filter_lists
return filter_lists
def get_unfiltered_parts(filters: list[WorkflowItem]) -> int:
attr_dict = {
"x": {">": 0, "<": 4001},
"m": {">": 0, "<": 4001},
"a": {">": 0, "<": 4001},
"s": {">": 0, "<": 4001},
}
for item in filters:
match item.check_comp:
case ">":
attr_dict[item.check_attr][">"] = max(attr_dict[item.check_attr][">"], item.check_value)
case "<":
attr_dict[item.check_attr]["<"] = min(attr_dict[item.check_attr]["<"], item.check_value)
case _:
assert False, f"unexpected check comp {item.check_comp}, {item.check_target}"
return mul([v["<"] - v[">"] - 1 for v in attr_dict.values()])
class Day(AOCDay):
inputs = [
[
(19114, "input19_test"),
(397643, "input19"),
],
[
(167409079868000, "input19_test"),
(132392981697081, "input19"),
],
]
def parse_input(self) -> (dict[str, Workflow], list[Part]):
workflow_list, parts = self.getMultiLineInputAsArray()
workflow_list = [Workflow(x) for x in workflow_list]
parts = [Part(x) for x in parts]
workflows = {}
for w in workflow_list:
workflows[w.name] = w
ps = [x for x in workflow_list if x.has_target(w.name)]
if ps:
assert len(ps) == 1
w.source = ps[0]
return workflows, parts
def part1(self) -> Any:
workflows, parts = self.parse_input()
ans = 0
for part in parts:
target = workflows["in"].get_target(part)
while target not in ["A", "R"]:
target = workflows[target].get_target(part)
if target == "A":
ans += part.get_value()
return ans
def part2(self) -> Any:
workflows, _ = self.parse_input()
ans = 0
for workflow in workflows.values():
if workflow.has_target("A"):
filters = workflow.reached_workflow("A")
ans += sum(get_unfiltered_parts(x) for x in filters)
return ans
if __name__ == "__main__":
day = Day(2023, 19)
day.run(verbose=True)