from typing import Tuple, List def browse_schema(schema): total_parts = 0 buf = [] max_row, max_col = len(schema), len(schema[0]) for y in range(max_row): for x in range(max_col): item = schema[y][x] if item.isnumeric(): # continue parsing full number buf.append(item) elif buf: # end of a number, do the engine part check if buf: neighbors = get_neighbors((x, y), len(buf), schema) if is_engine_part(neighbors): part_number = int("".join(buf)) total_parts += part_number buf.clear() # reached end of a number, clear buffer print(f"Part 1, sum of the parts numbers = {total_parts}") print(f"Part 2, sum of the gear ratios = TODO") def is_engine_part(neighbors: List[str]) -> bool: # get list of symbols (not '.', \n or a number) symbols = filter(lambda x: not x.isnumeric() and not x in (".", "\n"), neighbors) return next(symbols, None) is not None def get_neighbors(pos: Tuple[int, int], length: int, schema: List[List[str]]) -> List[str]: x, y = pos start_x = x - length neighbors = [get_neighbors_of_digit((x, y), schema) for x in range(start_x, x)] neighbors = [item for sublist in neighbors for item in sublist] # flatten list of list return neighbors def get_neighbors_of_digit(pos: Tuple[int, int], schema: List[List[str]]) -> List[str]: max_row, max_col = len(schema), len(schema[0]) x, y = pos neighbors = [] # top if y-1 >= 0: neighbors.append(schema[y-1][x]) # bottom: if y+1 < max_row: neighbors.append(schema[y+1][x]) # left if x-1 >= 0: neighbors.append(schema[y][x-1]) # right if x+1 < max_col: neighbors.append(schema[y][x+1]) # top-left if y-1 >= 0 and x-1 >= 0: neighbors.append(schema[y-1][x-1]) # top-right if y-1 >= 0 and x+1 < max_col: neighbors.append(schema[y-1][x+1]) # bottom-left if y+1 < max_row and x-1 >= 0: neighbors.append(schema[y+1][x-1]) # bottom-right if y+1 < max_row and x+1 < max_col: neighbors.append(schema[y+1][x+1]) return neighbors if __name__ == "__main__": import sys infile = sys.argv[1] with open(infile) as f: schema = [[c for c in line] for line in f.readlines()] browse_schema(schema)