• 2 Posts
  • 24 Comments
Joined 1 year ago
cake
Cake day: July 1st, 2023

help-circle













  • Python 3

    I’m trying to practice writing clear, commented, testable functions, so I added some things that are strictly unnecessary for the challenge (docstrings, error raising, type hints, tests…), but I think it’s a necessary exercise for me. If anyone has comments or criticism about my attempt at “best practices,” please let me know!

    Also, I thought it was odd that the correct answer to part 2 requires that you allow for overlapping letters such as “threeight”, but that doesn’t occur in the sample input. I imagine that many people will hit a wall wondering why their answer is rejected.

    day01.py
    import re
    from pathlib import Path
    
    
    DIGITS = [
        "zero",
        "one",
        "two",
        "three",
        "four",
        "five",
        "six",
        "seven",
        "eight",
        "nine",
        r"\d",
    ]
    
    PATTERN_PART_1 = r"\d"
    PATTERN_PART_2 = f"(?=({'|'.join(DIGITS)}))"
    
    
    def get_digit(s: str) -> int:
        """Return the digit in the input
    
        Args:
            s (str): one string containing a single digit represented by a single arabic numeral or spelled out in lower-case English
    
        Returns:
            int: the digit as an integer value
        """
    
        try:
            return int(s)
        except ValueError:
            return DIGITS.index(s)
    
    
    def calibration_value(line: str, pattern: str) -> int:
        """Return the calibration value in the input
    
        Args:
            line (str): one line containing a calibration value
            pattern (str): the regular expression pattern to match
    
        Raises:
            ValueError: if no digits are found in the line
    
        Returns:
            int: the calibration value
        """
    
        digits = re.findall(pattern, line)
    
        if digits:
            return get_digit(digits[0]) * 10 + get_digit(digits[-1])
    
        raise ValueError(f"No digits found in: '{line}'")
    
    
    def calibration_sum(lines: str, pattern: str) -> int:
        """Return the sum of the calibration values in the input
    
        Args:
            lines (str): one or more lines containing calibration values
    
        Returns:
            int: the sum of the calibration values
        """
    
        sum = 0
    
        for line in lines.split("\n"):
            sum += calibration_value(line, pattern)
    
        return sum
    
    
    if __name__ == "__main__":
        path = Path(__file__).resolve().parent / "input" / "day01.txt"
    
        lines = path.read_text().strip()
    
        print("Sum of calibration values:")
        print(f"• Part 1: {calibration_sum(lines, PATTERN_PART_1)}")
        print(f"• Part 2: {calibration_sum(lines, PATTERN_PART_2)}")
    
    test_day01.py
    import pytest
    from advent_2023_python.day01 import (
        calibration_value,
        calibration_sum,
        PATTERN_PART_1,
        PATTERN_PART_2,
    )
    
    
    LINES_PART_1 = [
        ("1abc2", 12),
        ("pqr3stu8vwx", 38),
        ("a1b2c3d4e5f", 15),
        ("treb7uchet", 77),
    ]
    BLOCK_PART_1 = (
        "\n".join([line[0] for line in LINES_PART_1]),
        sum(line[1] for line in LINES_PART_1),
    )
    
    LINES_PART_2 = [
        ("two1nine", 29),
        ("eightwothree", 83),
        ("abcone2threexyz", 13),
        ("xtwone3four", 24),
        ("4nineeightseven2", 42),
        ("zoneight234", 14),
        ("7pqrstsixteen", 76),
    ]
    BLOCK_PART_2 = (
        "\n".join([line[0] for line in LINES_PART_2]),
        sum(line[1] for line in LINES_PART_2),
    )
    
    
    def test_part_1():
        for line in LINES_PART_1:
            assert calibration_value(line[0], PATTERN_PART_1) == line[1]
    
        assert calibration_sum(BLOCK_PART_1[0], PATTERN_PART_1) == BLOCK_PART_1[1]
    
    
    def test_part_2_with_part_1_values():
        for line in LINES_PART_1:
            assert calibration_value(line[0], PATTERN_PART_2) == line[1]
    
        assert calibration_sum(BLOCK_PART_1[0], PATTERN_PART_2) == BLOCK_PART_1[1]
    
    
    def test_part_2_with_part_2_values():
        for line in LINES_PART_2:
            assert calibration_value(line[0], PATTERN_PART_2) == line[1]
    
        assert calibration_sum(BLOCK_PART_2[0], PATTERN_PART_2) == BLOCK_PART_2[1]
    
    
    def test_no_digits():
        with pytest.raises(ValueError):
            calibration_value("abc", PATTERN_PART_1)
    
        with pytest.raises(ValueError):
            calibration_value("abc", PATTERN_PART_2)