diff --git a/2023/python/Day10.py b/2023/python/Day10.py new file mode 100644 index 0000000000000000000000000000000000000000..55cd7bf68a0e240a44cdec78f3a27395ed04bdab --- /dev/null +++ b/2023/python/Day10.py @@ -0,0 +1,202 @@ +import functools +from typing import List, Tuple + +from ImportData import import_data + +day: int = 10 + +# sample_data: List[str] = ''' +# ..... +# .S-7. +# .|.|. +# .L-J. +# ..... +# '''.split('\n')[1:-1] +# sample_data: List[str] = ''' +# 7-F7- +# .FJ|7 +# SJLL7 +# |F--J +# LJ.LJ +# '''.split('\n')[1:-1] +# sample_data: List[str] = ''' +# ........... +# .S-------7. +# .|F-----7|. +# .||.....||. +# .||.....||. +# .|L-7.F-J|. +# .|..|.|..|. +# .L--J.L--J. +# ........... +# '''.split('\n')[1:-1] +# sample_data: List[str] = ''' +# .......... +# .S------7. +# .|F----7|. +# .||....||. +# .||....||. +# .|L-7F-J|. +# .|..||..|. +# .L--JL--J. +# .......... +# '''.split('\n')[1:-1] +# sample_data: List[str] = ''' +# .F----7F7F7F7F-7.... +# .|F--7||||||||FJ.... +# .||.FJ||||||||L7.... +# FJL7L7LJLJ||LJ.L-7.. +# L--J.L7...LJS7F-7L7. +# ....F-J..F7FJ|L7L7L7 +# ....L7.F7||L7|.L7L7| +# .....|FJLJ|FJ|F7|.LJ +# ....FJL-7.||.||||... +# ....L---J.LJ.LJLJ... +# '''.split('\n')[1:-1] +sample_data: List[str] = ''' +FF7FSF7F7F7F7F7F---7 +L|LJ||||||||||||F--J +FL-7LJLJ||||||LJL-77 +F--JF--7||LJLJ7F7FJ- +L---JF-JLJ.||-FJLJJ7 +|F|F-JF---7F7-L7L|7| +|FFJF7L7F-JF7|JL---7 +7-L-JL7||F7|L7F-7F7| +L.L7LFJ|||||FJL7||LJ +L7JLJL-JLJLJL--JLJ.L +'''.split('\n')[1:-1] + +data_structure: type = Tuple[int, int, str, List[str]] + + +def parse_data(data: List[str]) -> data_structure: + initial_row: int = -1 + initial_column: int = -1 + initial_pipe: str + number_of_rows = len(data) + number_of_columns = len(data[0]) + for row in range(number_of_rows): + for column in range(number_of_columns): + if data[row][column] == 'S': + initial_row = row + initial_column = column + initial_pipe = input('What is the shape of the initial pipe? ') + return initial_row, initial_column, initial_pipe, data + + +def part1(data: data_structure) -> int: + row, column, pipe, pipes = data + previous_locations: List[Tuple[int, int]] = [(row, column), (row, column)] + current_locations: List[Tuple[int, int]] + match pipe: + case '|': + current_locations = [(row + 1, column), (row - 1, column)] + case '-': + current_locations = [(row, column + 1), (row, column - 1)] + case 'L': + current_locations = [(row, column + 1), (row - 1, column)] + case 'J': + current_locations = [(row, column - 1), (row - 1, column)] + case '7': + current_locations = [(row, column - 1), (row + 1, column)] + case 'F': + current_locations = [(row, column + 1), (row + 1, column)] + case _: + raise ValueError(f'{pipe} at ({row},{column})') + distance: int = 1 + while current_locations[0] != current_locations[1]: + distance += 1 + for i in range(len(current_locations)): + previous_row, previous_column = previous_locations[i] + row, column = current_locations[i] + pipe = pipes[row][column] + previous_locations[i] = current_locations[i] + row_change = row - previous_row + column_change = column - previous_column + match pipe: + case '|' | '-': + current_locations[i] = (row + row_change, column + column_change) + case 'L' | '7': + current_locations[i] = (row + column_change, column + row_change) + case 'J' | 'F': + current_locations[i] = (row - column_change, column - row_change) + case _: + raise ValueError(f'{pipe} at ({row}, {column})') + return distance + + +def part2(data: data_structure) -> int: + row, column, pipe, pipes = data + # an extra copy of the pipes data, "zoomed in" to allow for gaps between pipes + tiles: [List[List[str]]] = [['I'] * (2 * len(pipes[0])) for _ in range(2 * len(pipes))] + # first, draw the main loop on the tiles copy + previous_location: Tuple[int, int] = (row, column) + current_location: Tuple[int, int] + match pipe: + case '|' | 'F': + current_location = (row + 1, column) + tiles[2 * row + 1][2 * column] = 'X' + case '-' | 'L': + current_location = (row, column + 1) + tiles[2 * row][2 * column + 1] = 'X' + case 'J' | '7': + current_location = (row, column - 1) + tiles[2 * row][2 * column - 1] = 'X' + case _: + raise ValueError(f'{pipe} at ({row},{column})') + previous_row, previous_column = previous_location + row, column = current_location + while tiles[2 * row][2 * column] != 'X': + tiles[2 * row][2 * column] = 'X' + pipe = pipes[row][column] + previous_location = current_location + row_change = row - previous_row + column_change = column - previous_column + match pipe: + case '|' | '-': + current_location = (row + row_change, column + column_change) + tiles[2 * row + row_change][2 * column + column_change] = 'X' + case 'L' | '7': + current_location = (row + column_change, column + row_change) + tiles[2 * row + column_change][2 * column + row_change] = 'X' + case 'J' | 'F': + current_location = (row - column_change, column - row_change) + tiles[2 * row - column_change][2 * column - row_change] = 'X' + case 'S': + pass + case _: + raise ValueError(f'{pipe} at ({row}, {column})') + previous_row, previous_column = previous_location + row, column = current_location + # now let's create a border + tiles = [['O'] * (2 * len(pipes[0]))] + tiles + [['O'] * (2 * len(tiles[0]))] + tiles = [['O'] + tile + ['O'] for tile in tiles] + # fill in the "outside" + finished: bool = False + while not finished: + finished = True + for row in range(1, len(tiles) - 1): + for column in range(1, len(tiles[0]) - 1): + if tiles[row][column] == 'I' and (tiles[row - 1][column] == 'O' or tiles[row][column - 1] == 'O' or + tiles[row + 1][column] == 'O' or tiles[row][column + 1] == 'O'): + tiles[row][column] = 'O' + finished = False + # now shrink it back down to size, step 1: remove the border + # arguably, this step is unnecessary, but it simplifies the expression in step 2 + tiles = [row[1:-1] for row in tiles[1:-1]] + # shrink it back down to size, step 2: remove the "in between" tiles + new_tiles: List[List[str]] = [] + for row in range(0, len(tiles), 2): + new_row: List[str] = [] + for column in range(0, len(tiles[0]), 2): + new_row.append(tiles[row][column]) + new_tiles.append(new_row) + # count the "inside" + return sum([functools.reduce(lambda x, y: x + y, map(lambda x: 1 if x == 'I' else 0, row)) for row in new_tiles]) + + +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)))