From d6bd55ad1b0b79541a35f819cd177b3e27c84a05 Mon Sep 17 00:00:00 2001 From: josh Date: Tue, 19 May 2026 21:31:44 +0000 Subject: [PATCH] add-day2 (#1) Reviewed-on: https://git.joshuaschuett.com/projects/misc/pulls/1 Co-authored-by: josh Co-committed-by: josh --- .gitignore | 1 + day2/day2_sample.py | 96 ++++++++++++++++++++++++++++++++++ day2/test_cases/all_safe.txt | 5 ++ day2/test_cases/all_unsafe.txt | 6 +++ day2/test_cases/edge_cases.txt | 5 ++ day2/test_cases/sample.txt | 6 +++ day2/test_day2.py | 26 +++++++++ 7 files changed, 145 insertions(+) create mode 100644 .gitignore create mode 100644 day2/day2_sample.py create mode 100644 day2/test_cases/all_safe.txt create mode 100644 day2/test_cases/all_unsafe.txt create mode 100644 day2/test_cases/edge_cases.txt create mode 100644 day2/test_cases/sample.txt create mode 100644 day2/test_day2.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/day2/day2_sample.py b/day2/day2_sample.py new file mode 100644 index 0000000..cbb1fda --- /dev/null +++ b/day2/day2_sample.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +""" +Counts how many reports are "safe". + +A report (one line of space-separated integers) is safe if: +1) Levels are strictly monotonic (all increasing OR all decreasing). +2) Adjacent levels differ by at least 1 and at most 3 (inclusive). + +Usage: + python day2/aoc_2024_day2.py input.txt +""" + +import argparse + + +class Report: + def __init__(self, levels): + self.levels = levels + + @classmethod + def from_line(cls, line): + parts = line.split() + levels = [] + for part in parts: + levels.append(int(part)) + return cls(levels=levels) + + def is_safe(self): + if len(self.levels) < 2: + return True + + # Determine direction (increasing/decreasing) from the first valid step. + # 0 => not decided yet + # +1 => increasing + # -1 => decreasing + direction = 0 + + for i in range(1, len(self.levels)): + previous_level = self.levels[i - 1] + current_level = self.levels[i] + delta = current_level - previous_level + + # Adjacent levels must change (no equals). + if delta == 0: + return False + + # Differ by at least one and at most three. + if abs(delta) > 3: + return False + + step_direction = 1 if delta > 0 else -1 + if direction == 0: + direction = step_direction + # The levels are either all increasing or all decreasing. + elif step_direction != direction: + return False + + return True + + +def parse_reports(path): + reports = [] + with open(path, "r", encoding="utf-8") as f: + for raw in f: + line = raw.strip() + if not line: + continue + reports.append(Report.from_line(line)) + return reports + + +def count_safe(reports): + safe_count = 0 + for report in reports: + if report.is_safe(): + safe_count += 1 + return safe_count + + +def count_safe_in_file(path): + return count_safe(parse_reports(path)) + + +def main(argv=None): + parser = argparse.ArgumentParser("CLI to read sample files.") + parser.add_argument( + "input", + help="Input file path.", + ) + + args = parser.parse_args(argv) + return count_safe_in_file(args.input) + + +if __name__ == "__main__": + print(main()) \ No newline at end of file diff --git a/day2/test_cases/all_safe.txt b/day2/test_cases/all_safe.txt new file mode 100644 index 0000000..b51938e --- /dev/null +++ b/day2/test_cases/all_safe.txt @@ -0,0 +1,5 @@ +1 2 3 +3 2 1 +10 9 7 6 +1 4 7 + diff --git a/day2/test_cases/all_unsafe.txt b/day2/test_cases/all_unsafe.txt new file mode 100644 index 0000000..0564084 --- /dev/null +++ b/day2/test_cases/all_unsafe.txt @@ -0,0 +1,6 @@ +1 1 2 +1 5 6 +1 3 2 +5 2 3 +9 6 2 + diff --git a/day2/test_cases/edge_cases.txt b/day2/test_cases/edge_cases.txt new file mode 100644 index 0000000..4ed5a81 --- /dev/null +++ b/day2/test_cases/edge_cases.txt @@ -0,0 +1,5 @@ +5 +1 4 +4 1 +1 5 + diff --git a/day2/test_cases/sample.txt b/day2/test_cases/sample.txt new file mode 100644 index 0000000..2d6faf5 --- /dev/null +++ b/day2/test_cases/sample.txt @@ -0,0 +1,6 @@ +7 6 4 2 1 +1 2 7 8 9 +9 7 6 2 1 +1 3 2 4 5 +8 6 4 4 1 +1 3 6 7 9 \ No newline at end of file diff --git a/day2/test_day2.py b/day2/test_day2.py new file mode 100644 index 0000000..48a59f1 --- /dev/null +++ b/day2/test_day2.py @@ -0,0 +1,26 @@ +import os +import unittest + +from day2_sample import count_safe_in_file + + +class TestDay2(unittest.TestCase): + def _case_path(self, filename): + here = os.path.dirname(__file__) + return os.path.join(here, "test_cases", filename) + + def test_sample(self): + self.assertEqual(count_safe_in_file(self._case_path("sample.txt")), 2) + + def test_all_safe(self): + self.assertEqual(count_safe_in_file(self._case_path("all_safe.txt")), 4) + + def test_all_unsafe(self): + self.assertEqual(count_safe_in_file(self._case_path("all_unsafe.txt")), 0) + + def test_edge_cases(self): + self.assertEqual(count_safe_in_file(self._case_path("edge_cases.txt")), 3) + + +if __name__ == "__main__": + unittest.main()