| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 | from helpers import debug, load_input
def is_digit(n):
    try:
        m = int(n)
    except ValueError:
        return False
    return True
class InputNumber:
    def __init__(self, number, start_pos, end_pos, line):
        self.number = number
        self.start_pos = start_pos
        self.end_pos = end_pos
        self.line = line
    def _is_symbol(self, char):
        return (char != "." and not is_digit(char))
    def _check_up(self, input):
        if self.line == 0:
            return False
        cur_line = input[self.line - 1]
        for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)):
            char = cur_line[i]
            if self._is_symbol(char):
                debug(f"Found symbol next to {self.number} at line {self.line-1}: {i}")
                return True
        return False
    
    def _check_down(self, input):
        if self.line == len(input)-1:
            return False
        cur_line = input[self.line + 1]
        for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)):
            char = cur_line[i]
            if self._is_symbol(char):
                debug(f"Found symbol next to {self.number} at line {self.line+1}: {i}")
                return True
        return False
    
    def _check_left(self, input):
        if self.start_pos == 0:
            debug("Already at the left-hand side of the line")
            return False
        if self._is_symbol(input[self.line][self.start_pos-1]):
            debug(f"Found symbol next to {self.number} at line {self.line}: {self.start_pos-1}")
            return True
        return False
    def _check_right(self, input):
        if self.end_pos == len(input[0]) - 1:
            debug("Already at the right-hand side of the line")
            return False
        if self._is_symbol(input[self.line][self.end_pos+1]):
            debug(f"Found symbol next to {self.number} at line {self.line}: {self.end_pos+1}")
            return True
        return False
    
    def is_part_number(self, input):
        return (
            self._check_up(input)
            or self._check_down(input)
            or self._check_left(input)
            or self._check_right(input)
        )
        
def main():
    lines = load_input(3)
    # Input is lines of periods (.), numbers (0-9), and symbols 
    # (anything that isn't a period or number)
    # Any number adjacent (horizontally, vertically, diagonally)
    # to a symbol is a part number. Get the sum of the part numbers.
    max_len = len(lines[0]) # all lines are the same length
    positions = []
    for j, line in enumerate(lines):
        i = 0
        while i < max_len:
            if is_digit(line[i]):
                current_number = ""
                start_pos = i
                while i < max_len and is_digit(line[i]):
                    current_number += f"{line[i]}"
                    i += 1
                end_pos = i
                input_number = InputNumber(
                    number = int(current_number),
                    start_pos = start_pos,
                    end_pos = end_pos-1, # i is one greater than the end of the number
                    line = j
                )
                positions.append(input_number)
                debug(f"Found {input_number.number} at line {input_number.line} ({input_number.start_pos}, {input_number.end_pos})")
            i += 1
    # Now I have a list of the positions of all the numbers in the input.
    part_numbers = []
    for p in positions:
        if p.is_part_number(lines):
            part_numbers.append(p.number)
    
    print(sum(part_numbers))
if __name__ == "__main__":
    main()
 |