diff --git a/2023/python/Day03.py b/2023/python/Day03.py new file mode 100644 index 0000000000000000000000000000000000000000..8f04c08eb7f972c258d66686be120e41ce92dc1c --- /dev/null +++ b/2023/python/Day03.py @@ -0,0 +1,129 @@ +from typing import List, Optional + +from ImportData import import_data + +day: int = 3 + +sample_data: List[str] = ''' +467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.. +'''.split('\n')[1:-1] + +data_structure: type = List[str] + + +def parse_data(data: List[str]) -> data_structure: + # add some padding so that we won't have to look for borders later + padded_data: List[str] = [] + for string in data: + padded_data.append(f' {string} ') + length = len(padded_data[0]) + padded_data.append('.' * length) + padded_data.insert(0, '.' * length) + return padded_data + + +def is_part_number(data: data_structure, row: int, first_column: int, last_column: int) -> bool: + symbols = frozenset({'!', '@', '#', '$', '%', '^', '&', '*', '-', '+', '=', '/', '?'}) + found_symbol: bool = False + for symbol in symbols: + if symbol in data[row - 1][first_column - 1:last_column + 1] \ + or symbol in data[row][first_column - 1:last_column + 1] \ + or symbol in data[row + 1][first_column - 1:last_column + 1]: + found_symbol = True + return found_symbol + + +def part1(data: data_structure) -> int: + number: Optional[int] = None + first_column: int = 0 + last_column: int + part_numbers: List[int] = [] + for row, string in enumerate(data): + for column, character in enumerate(string): + if character.isdigit(): + if number is None: + number = int(character) + first_column = column + else: + number = 10 * number + int(character) + else: + if number is not None: + last_column = column + if is_part_number(data, row, first_column, last_column): + part_numbers.append(number) + number = None + return sum(part_numbers) + + +class Gear: + gears: List["Gear"] = [] + + def __init__(self, row: int, column: int): + self.row = row + self.column = column + self.numbers: List[int] = [] + + @classmethod + def add_number_to_gear(cls, number: int, row: int, column: int): + candidates = [gear for gear in Gear.gears if gear.row == row and gear.column == column] + gear: Gear + if candidates: + gear = candidates[0] + else: + gear = Gear(row, column) + Gear.gears.append(gear) + gear.numbers.append(number) + + +def look_for_gear(number: int, data: data_structure, row: int, first_column: int, last_column: int): + try: + column = data[row - 1].index('*', first_column - 1, last_column + 1) + Gear.add_number_to_gear(number, row - 1, column) + except ValueError: + pass + try: + column = data[row].index('*', first_column - 1, last_column + 1) + Gear.add_number_to_gear(number, row, column) + except ValueError: + pass + try: + column = data[row + 1].index('*', first_column - 1, last_column + 1) + Gear.add_number_to_gear(number, row + 1, column) + except ValueError: + pass + + +def part2(data: data_structure) -> int: + number: Optional[int] = None + first_column: int = 0 + last_column: int + for row, string in enumerate(data): + for column, character in enumerate(string): + if character.isdigit(): + if number is None: + number = int(character) + first_column = column + else: + number = 10 * number + int(character) + else: + if number is not None: + last_column = column + look_for_gear(number, data, row, first_column, last_column) + number = None + return sum([gear.numbers[0] * gear.numbers[1] for gear in Gear.gears if len(gear.numbers) == 2]) + + +if __name__ == '__main__': + production_ready = True + raw_data = import_data(day) if production_ready else sample_data + print(part1(parse_data(raw_data))) + print(part2(parse_data(raw_data)))