diff --git a/day19/day19.py b/day19/day19.py index b63854a..f8d227d 100644 --- a/day19/day19.py +++ b/day19/day19.py @@ -6,12 +6,14 @@ """ parsing classes section """ +INPUT = "day19/input.txt" +INPUT_SMALL = "day19/input-small.txt" -def get_input() -> tuple[list[Workflow], list[Part]]: +def get_input(path: str) -> tuple[list[Workflow], list[Part]]: workflows: list[Workflow] = [] parts: list[Part] = [] - with open("day19/input.txt", encoding="utf8") as file: + with open(path, encoding="utf8") as file: for line in file: if len(line.strip()) == 0: break @@ -24,20 +26,20 @@ def get_input() -> tuple[list[Workflow], list[Part]]: return (workflows, parts) -def process_part(workflows: dict[str, Workflow], part: Part) -> bool: +def process_part(workflows: dict[str, Workflow], part: Part) -> int: # ends are `A` and R # start is `in` workflow = workflows["in"] while True: workflow_name = workflow.process_part(part) if workflow_name == "A": - return True + return part.rating if workflow_name == "R": - return False + return 0 workflow = workflows[workflow_name] -def solve_part2(workflows: dict[str, Workflow]) -> None: +def solve_part2(workflows: dict[str, Workflow]) -> int: min_xmas = Part(1, 1, 1, 1) max_xmas = Part(4001, 4001, 4001, 4001) part_range = PartRange(min_xmas, max_xmas) @@ -57,23 +59,27 @@ def solve_part2(workflows: dict[str, Workflow]) -> None: result += item.part_range.size() elif item.destination != "R": to_process.put(item) - print(result) + return result -def main() -> None: - # combined - workflows, parts = get_input() +def part1(workflows: list[Workflow], parts: list[Part]) -> int: workflows_mapping: dict[str, Workflow] = {wf.name: wf for wf in workflows} + total = sum(process_part(workflows_mapping, part) for part in parts) + + return total - # part 1 - total = 0 - for part in parts: - if process_part(workflows_mapping, part): - total += part.rating - print(total) - # part 2 - solve_part2(workflows_mapping) +def part2(workflows: list[Workflow]) -> int: + workflows_mapping: dict[str, Workflow] = {wf.name: wf for wf in workflows} + return solve_part2(workflows_mapping) + + +def main() -> None: + # combined + workflows, parts = get_input(INPUT) + + print(part1(workflows, parts)) + print(part2(workflows)) if __name__ == "__main__": diff --git a/day19/lib/classes.py b/day19/lib/classes.py index af039a8..3aabd1c 100644 --- a/day19/lib/classes.py +++ b/day19/lib/classes.py @@ -177,7 +177,7 @@ def process_part_range( return None, fail -@dataclass +@dataclass(eq=True) class Workflow: name: str rules: list[Rule] diff --git a/day19/tests/test_day19.py b/day19/tests/test_day19.py new file mode 100644 index 0000000..946c59d --- /dev/null +++ b/day19/tests/test_day19.py @@ -0,0 +1,8 @@ +from day19.day19 import INPUT_SMALL, get_input, part1, part2 + + +def test_day19() -> None: + workflows, parts = get_input(INPUT_SMALL) + + assert part1(workflows, parts) == 19114 + assert part2(workflows) == 167409079868000 diff --git a/day19/tests/test_parsers.py b/day19/tests/test_parsers.py new file mode 100644 index 0000000..216f34c --- /dev/null +++ b/day19/tests/test_parsers.py @@ -0,0 +1,65 @@ +"""parsers""" +from day19.lib.classes import Comparator, Component, Condition, Part, Rule, Workflow +from day19.lib.parsers import ( + parse_condition_string, + parse_part_string, + parse_rule_string, + parse_workflow_string, +) + + +def test_parse_part_string() -> None: + part: Part = parse_part_string("{x=787,m=2655,a=1222,s=2876}\n") + assert part == Part(787, 2655, 1222, 2876) + + part = parse_part_string("{x=1,m=2,a=3,s=4}\n") + assert part == Part(1, 2, 3, 4) + + +def test_parse_workflow_string() -> None: + """ + returns a workflow from a string representation + `px{a<2006:qkq,m>2090:A,rfg}\n` + """ + workflow: Workflow = parse_workflow_string("px{a<2006:qkq,m>2090:A,rfg}\n") + rules = [ + Rule("qkq", Condition(Component.A, Comparator.LessThan, 2006)), + Rule("A", Condition(Component.M, Comparator.GreaterThan, 2090)), + Rule("rfg", None), + ] + workflow2 = Workflow("px", rules) + assert workflow == workflow2 # confirm that workflow.eq works + assert workflow.name == workflow2.name + assert workflow.rules[0] == workflow2.rules[0] + assert workflow.rules[1] == workflow2.rules[1] + assert workflow.rules[2] == workflow2.rules[2] + assert workflow.rules == workflow2.rules + + +def test_parse_rule_string() -> None: + """ + `a<2006:qkq` or `rfg` + """ + rule: Rule = parse_rule_string("a<2006:qkq") + rule2: Rule = Rule("qkq", Condition(Component.A, Comparator.LessThan, 2006)) + + assert rule.destination == rule2.destination + assert rule.condition == rule2.condition + assert rule == rule2 + + rule = parse_rule_string("rfg") + rule2 = Rule("rfg") + + assert rule.destination == rule2.destination + assert rule.condition == rule2.condition + assert rule == rule2 + + +def test_parse_condition_string() -> None: + """a<2006""" + condition: Condition = parse_condition_string("a<2006") + condition2: Condition = Condition(Component.A, Comparator.LessThan, 2006) + assert condition == condition2 + assert condition.component == condition2.component + assert condition.sign == condition2.sign + assert condition.value == condition2.value