from functools import total_ordering def run(): engine = Engine(r"src/assets/day_3.txt") print(f"Part number sum: {engine.sum_part_numbers()}") print(f"Gear ratio: {engine.get_gear_ratio()}") class Engine: engine: list[str] neutral_char: str = "." gear_char: str = "*" def __init__(self, path: str): self.engine = self.read_engine(path) def get_gear_ratio(self) -> int: sum = 0 for x in range(self.engine.__len__()): for y in range(self.engine[x].__len__()): gear = self.position_is_gear(x, y) if gear[0] == True: n_1 = self.extract_number_from_position( gear[1][0][0], gear[1][0][1] ) n_2 = self.extract_number_from_position( gear[1][1][0], gear[1][1][1] ) sum += n_1[0] * n_2[0] return sum def sum_part_numbers(self) -> int: sum = 0 for x in range(self.engine.__len__()): y = 0 while y < self.engine[x].__len__(): if ( self.engine[x][y] != self.neutral_char and not self.position_is_symbol(x, y) and self.position_has_part(x, y) ): number_pos = self.extract_number_from_position(x, y) sum += number_pos[0] y = number_pos[1] - 1 y += 1 return sum def extract_number_from_position(self, x: int, y: int) -> tuple[int, int]: n = "" while self.position_is_number(x, y): y -= 1 y += 1 while self.position_is_number(x, y): n += self.engine[x][y] y += 1 return (int(n), y) def position_is_gear(self, x: int, y: int) -> tuple[bool, list[tuple[int, int]]]: if x >= self.engine.__len__() or x < 0 or y >= self.engine.__len__() or y < 0: return (False, []) if self.engine[x][y] != self.gear_char: return (False, []) total_numbers = 0 position_number: list[tuple[int, int]] = [] b = self.position_is_number(x + 1, y) t = self.position_is_number(x - 1, y) r = self.position_is_number(x, y + 1) l = self.position_is_number(x, y - 1) bl = self.position_is_number(x + 1, y - 1) br = self.position_is_number(x + 1, y + 1) tl = self.position_is_number(x - 1, y - 1) tr = self.position_is_number(x - 1, y + 1) if r is True: position_number.append((x, y + 1)) total_numbers += 1 if l is True: position_number.append((x, y - 1)) total_numbers += 1 if tl is True: position_number.append((x - 1, y - 1)) total_numbers += 1 elif t is True: position_number.append((x - 1, y)) total_numbers += 1 if tr is True and t is not True: position_number.append((x - 1, y + 1)) total_numbers += 1 if bl is True: position_number.append((x + 1, y - 1)) total_numbers += 1 elif b is True: position_number.append((x + 1, y)) total_numbers += 1 if br is True and b is not True: position_number.append((x + 1, y + 1)) total_numbers += 1 return (total_numbers == 2, position_number) def position_has_part(self, x: int, y: int) -> bool: return ( self.position_is_symbol(x + 1, y) or self.position_is_symbol(x - 1, y) or self.position_is_symbol(x, y + 1) or self.position_is_symbol(x, y - 1) or self.position_is_symbol(x + 1, y + 1) or self.position_is_symbol(x - 1, y - 1) or self.position_is_symbol(x + 1, y - 1) or self.position_is_symbol(x - 1, y + 1) ) def position_is_symbol(self, x: int, y: int) -> bool: if x >= self.engine.__len__() or x < 0 or y >= self.engine.__len__() or y < 0: return False try: int(self.engine[x][y]) return False except: return self.engine[x][y] != self.neutral_char def position_is_number(self, x: int, y: int) -> bool: if x >= self.engine.__len__() or x < 0 or y >= self.engine.__len__() or y < 0: return False try: int(self.engine[x][y]) return True except: return False def read_engine(self, path: str) -> list[str]: with open(path) as file: return file.readlines()