diff --git a/dsa/advent-of-code/2023/day_23.py b/dsa/advent-of-code/2023/day_23.py index f441060..1fc8469 100644 --- a/dsa/advent-of-code/2023/day_23.py +++ b/dsa/advent-of-code/2023/day_23.py @@ -1,6 +1,6 @@ -import sys import string from itertools import cycle +from collections import defaultdict VECTORS = [(0, 1), (0, -1), (-1, 0), (1, 0)] VECTOR_BY_CHAR = { @@ -84,6 +84,54 @@ def is_path(y2, x2): return result +def precompute_optimized_graph(grid, part): + graph = defaultdict(list) # pos => [(pos, distance)] + + def find_junctions(): + junctions = set() + for y, row in enumerate(grid): + for x, _ in enumerate(row): + if grid[y][x] == "#": + continue + pos = (y, x) + if len(neighbors(pos, grid, part)) > 2: + junctions.add(pos) + return junctions + + def find_next_junction_or_end(pos, path, step_count=1): + if pos == junction: + return + + path.add(pos) + + if pos in junctions or pos == end_pos(grid): + return (pos, step_count) + + n = [x for x in neighbors(pos, grid, part) if x not in path] + + if len(n) == 0: + return + if len(n) != 1: + print("unexpected condition") + exit() + return find_next_junction_or_end(n[0], path, step_count + 1) + + junctions = find_junctions() + + # compute distances between all junctions, including end pos + for junction in junctions: + for neighbor in neighbors(junction, grid, part): + next_junction = find_next_junction_or_end(neighbor, {junction}) + if next_junction is not None: + graph[junction].append(next_junction) + + start = start_pos(grid) + next_junction = find_next_junction_or_end(neighbors(start, grid, part)[0], {start}) + graph[start].append(next_junction) + + return graph + + def find_all_path_lengths(start_pos, end_pos, grid, part): all_path_lengths = [] @@ -98,6 +146,25 @@ def search(pos, path): new_path.add(adj_pos) search(adj_pos, new_path) + search(start_pos, path={start_pos}) + + return all_path_lengths + + +def find_all_path_lengths_optimized(start_pos, end_pos, graph): + all_path_lengths = [] + + def search(pos, path, distance=0): + if pos == end_pos: + all_path_lengths.append(distance) + return + + for adj_pos, d in graph[pos]: + if adj_pos not in path: + new_path = path.copy() + new_path.add(adj_pos) + search(adj_pos, new_path, distance + d) + path = set() path.add(start_pos) search(start_pos, path) @@ -107,15 +174,15 @@ def search(pos, path): def solution(data, part): grid = parsed(data) + graph = precompute_optimized_graph(grid, part) - all_path_lengths = find_all_path_lengths(start_pos(grid), end_pos(grid), grid, part) - - start_node_count = 1 - print(sorted(all_path_lengths)[-1] - start_node_count) + all_path_lengths = find_all_path_lengths_optimized( + start_pos(grid), end_pos(grid), graph + ) + print(max(all_path_lengths)) if __name__ == "__main__": - sys.setrecursionlimit(10000) with open("day_23_input.txt", "r") as file: data = file.readlines() - solution(data, part=1) + solution(data, part=2) diff --git a/dsa/advent-of-code/2023/day_23_sample_with_axes.txt b/dsa/advent-of-code/2023/day_23_sample_with_axes.txt new file mode 100644 index 0000000..d327b0c --- /dev/null +++ b/dsa/advent-of-code/2023/day_23_sample_with_axes.txt @@ -0,0 +1,24 @@ + 01234567890123456789012 +0 #.##################### +1 #.......#########...### +2 #######.#########.#.### +3 ###.....#.>X>.###.#.### +4 ###v#####.#v#.###.#.### +5 ###X>...#.#.#.....#...# +6 ###v###.#.#.#########.# +7 ###...#.#.#.......#...# +8 #####.#.#.#######.#.### +9 #.....#.#.#.......#...# +0 #.#####.#.#.#########v# +1 #.#...#...#...###...>X# +2 #.#.#v#######v###.###v# +3 #...#.>.#...>X>.#.###.# +4 #####v#.#.###v#.#.###.# +5 #.....#...#...#.#.#...# +6 #.#########.###.#.#.### +7 #...###...#...#...#.### +8 ###.###.#.###v#####v### +9 #...#...#.#.>X>.#.>X### +0 #.###.###.#.###.#.#v### +1 #.....###...###...#...# +2 #####################.#