From 029a76b0885e63d9d0f6aef43928bc7231fbb468 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 5 Dec 2024 21:29:45 +1000 Subject: [PATCH] Part 1 solution for Day 5. --- day5/solver.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ day5/test.py | 46 +++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 day5/solver.py create mode 100644 day5/test.py diff --git a/day5/solver.py b/day5/solver.py new file mode 100644 index 0000000..894c4f8 --- /dev/null +++ b/day5/solver.py @@ -0,0 +1,75 @@ +from collections.abc import Iterable +import operator as op +import itertools as it +from collections import defaultdict +from typing import Iterator, TypeAlias +import logging +import sys + +logger = logging.getLogger() +logger.addHandler(logging.StreamHandler()) + + +RuleSet: TypeAlias = dict[str, set[str]] + + +def parse_rules(rule_lines: Iterable[str]) -> RuleSet: + """ + Parse a sequence of poset rules into a rulset + mapping id -> list of descendents. + """ + descendents = defaultdict(set) + for line in rule_lines: + if '|' not in line: + raise ValueError(f"Provided line '{line}' is not a rule.") + a, b = line.split('|') + descendents[a].add(b) + return descendents + +def check_line(line: list[str], ruleset: RuleSet) -> bool: + """ + Check whether the provided line satisfies the given rules. + """ + for i, char in enumerate(line): + rules = ruleset[char] + if any(prevchar in rules for prevchar in line[:i]): + return False + + return True + +def get_middle(line: list[str]) -> str: + """ + Get the middle of the line, if it exists. + """ + if not len(line) % 2: + raise ValueError("Even length line has no middle.") + return line[len(line) // 2] + +def process_data(lines: Iterator[str]): + lines = map(op.methodcaller('strip'), lines) + lines = it.dropwhile(op.not_, lines) + ruleset = parse_rules(it.takewhile(bool, lines)) + if logger.isEnabledFor(logging.DEBUG): + logger.debug(f"Ruleset: {ruleset}") + + total = 0 + for line in lines: + if line: + parts = line.split(',') + if check_line(parts, ruleset): + total += int(get_middle(parts)) + logger.debug(f"Valid line: {line}") + else: + logger.debug(f"Invalid line: {line}") + + return total + + +def main(): + with open(sys.argv[1]) as f: + total = process_data(f) + print(f"Total middle-sum is {total}") + + +if __name__ == '__main__': + main() diff --git a/day5/test.py b/day5/test.py new file mode 100644 index 0000000..34607ed --- /dev/null +++ b/day5/test.py @@ -0,0 +1,46 @@ +import logging +from solver import process_data + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + +test_data = r""" +47|53 +97|13 +97|61 +97|47 +75|29 +61|13 +75|53 +29|13 +97|29 +53|29 +61|53 +97|53 +61|29 +47|13 +75|47 +97|75 +47|61 +75|61 +47|29 +75|13 +53|13 + +75,47,61,53,29 +97,61,53,29,13 +75,29,13 +75,97,47,61,53 +61,13,29 +97,13,75,29,47 +""" + + +def test_main(): + print("Beginning basic test") + result = process_data(iter(test_data.splitlines())) + assert result == 143 + print("Basic test passed") + +if __name__ == '__main__': + test_main()