Solution to problem 10 in Python

This commit is contained in:
David Doblas Jiménez 2024-01-20 19:12:12 +01:00
parent 36d2884753
commit 90e3fb8ded

View File

@ -134,53 +134,235 @@
# it take to get from the starting position to the point farthest from the
# starting position?
import re
with open("files/P10.txt") as f:
p = [row for row in f.read().strip().split("\n")]
grid = [row for row in f.read().strip().split("\n")]
U, D, L, R = (-1, 0), (1, 0), (0, -1), (0, 1)
directions = {
"|": (U, D),
"-": (L, R),
"L": (U, R),
"J": (U, L),
"7": (D, L),
"F": (D, R),
".": (),
}
def bfs(grid, directions, connections):
start = [pos for pos, c in grid.items() if c == "S"][0]
queue, seen = [(0, start)], set()
while queue:
count, (x, y) = queue.pop(0)
seen.add((x, y))
for dx, dy in directions[grid[x, y]]:
new_x, new_y = x + dx, y + dy
if (new_x, new_y) not in grid or (new_x, new_y) in seen:
continue
if grid[new_x, new_y] not in connections[dx, dy]:
continue
queue.append((count + 1, (new_x, new_y)))
return count, seen
s_values = set(directions) - {"."}
# where is "S" located?
ss = [
(r, c)
for r, row in enumerate(grid)
for c, ch in enumerate(row)
if ch == "S"
]
# must be unique
assert len(ss) == 1
sr, sc = ss[0]
# if we are in the first row, we can't go up or if the above row does not let us
# go down
if sr == 0 or D not in directions[grid[sr - 1][sc]]:
# s is not on top of a pipe that point upwards
s_values -= set("|LJ")
# if we are in the last row, we can't go down or we can't go up from the char
# below us
if sr == len(grid) - 1 or U not in directions[grid[sr + 1][sc]]:
# s is not on top of a pipe that point downwards
s_values -= set("|7F")
# if we are in the first column, we can´t go left or if the pipe towards left
# does not go right
if sc == 0 or R not in directions[grid[sr][sc - 1]]:
s_values -= set("-J7")
# if we are in the last column, we can't go to the right or if the pipe towards
# right does not go left
if sc == len(grid[0]) - 1 or L not in directions[grid[sr][sc + 1]]:
s_values -= set("-LF")
# only one value is possible
assert len(s_values) == 1
[S] = s_values
# replace grid value of S with its true value
grid = [row.replace("S", S) for row in grid]
def part1():
N, S, W, E = (0, -1), (0, 1), (-1, 0), (1, 0)
directions = {
"S": [N, S, W, E],
"|": [N, S],
"-": [E, W],
"L": [N, E],
"J": [N, W],
"7": [S, W],
"F": [S, E],
}
connections = {
N: {"|", "7", "F"},
S: {"|", "L", "J"},
E: {"-", "J", "7"},
W: {"-", "L", "F"},
}
grid = {
(x, y): c
for y, row in enumerate(p)
for x, c in enumerate(row)
if c != "."
}
total, _ = bfs(grid, directions, connections)
# we will traverse our pipe to find the path length divided by 2, because we
# need to find the furthest point from the starting position
prev_r = sr
prev_c = sc
# we pick a random direction, first one for example
dr, dc = directions[grid[sr][sc]][0]
next_r = prev_r + dr
next_c = prev_c + dc
# # we count the start position
seen = {(sr, sc)}
while (next_r, next_c) != (sr, sc):
seen.add((next_r, next_c))
# for each direction we can take in the pipe for the next position
for dr, dc in directions[grid[next_r][next_c]]:
if (next_r + dr, next_c + dc) != (prev_r, prev_c):
# set previous to our current position
prev_r = next_r
prev_c = next_c
# moves to our next position
next_r += dr
next_c += dc
# we are done!
break
total = len(seen) // 2
print(f"You need {total} steps")
# for part 2
return grid, seen
# --- Part Two ---
# You quickly reach the farthest point of the loop, but the animal never
# emerges. Maybe its nest is within the area enclosed by the loop?
# To determine whether it's even worth taking the time to search for such a
# nest, you should calculate how many tiles are contained within the loop. For
# example:
# ...........
# .S-------7.
# .|F-----7|.
# .||.....||.
# .||.....||.
# .|L-7.F-J|.
# .|..|.|..|.
# .L--J.L--J.
# ...........
# The above loop encloses merely four tiles - the two pairs of . in the
# southwest and southeast (marked I below). The middle . tiles (marked O below)
# are not in the loop. Here is the same loop again with those regions marked:
# ...........
# .S-------7.
# .|F-----7|.
# .||OOOOO||.
# .||OOOOO||.
# .|L-7OF-J|.
# .|II|O|II|.
# .L--JOL--J.
# .....O.....
# In fact, there doesn't even need to be a full tile path to the outside for
# tiles to count as outside the loop - squeezing between pipes is also allowed!
# Here, I is still within the loop and O is still outside the loop:
# ..........
# .S------7.
# .|F----7|.
# .||OOOO||.
# .||OOOO||.
# .|L-7F-J|.
# .|II||II|.
# .L--JL--J.
# ..........
# In both of the above examples, 4 tiles are enclosed by the loop.
# Here's a larger example:
# .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...
# The above sketch has many random bits of ground, some of which are in the
# loop (I) and some of which are outside it (O):
# OF----7F7F7F7F-7OOOO
# O|F--7||||||||FJOOOO
# O||OFJ||||||||L7OOOO
# FJL7L7LJLJ||LJIL-7OO
# L--JOL7IIILJS7F-7L7O
# OOOOF-JIIF7FJ|L7L7L7
# OOOOL7IF7||L7|IL7L7|
# OOOOO|FJLJ|FJ|F7|OLJ
# OOOOFJL-7O||O||||OOO
# OOOOL---JOLJOLJLJOOO
# In this larger example, 8 tiles are enclosed by the loop.
# Any tile that isn't part of the main loop can count as being enclosed by the
# loop. Here's another example with many bits of junk pipe lying around that
# aren't connected to the main loop at all:
# 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
# Here are just the tiles that are enclosed by the loop marked with I:
# FF7FSF7F7F7F7F7F---7
# L|LJ||||||||||||F--J
# FL-7LJLJ||||||LJL-77
# F--JF--7||LJLJIF7FJ-
# L---JF-JLJIIIIFJLJJ7
# |F|F-JF---7IIIL7L|7|
# |FFJF7L7F-JF7IIL---7
# 7-L-JL7||F7|L7F-7F7|
# L.L7LFJ|||||FJL7||LJ
# L7JLJL-JLJLJL--JLJ.L
# In this last example, 10 tiles are enclosed by the loop.
# Figure out whether you have time to search for the nest by calculating the
# area within the loop. How many tiles are enclosed by the loop?
def part2(grid, seen):
# get rid of all garbage pipes
grid = [
"".join(ch if (r, c) in seen else "." for c, ch in enumerate(row))
for r, row in enumerate(grid)
]
inside = set()
for r, row in enumerate(grid):
# ignore pipes that face up or down on both ends because they don't matter
row = re.sub("L-*J|F-*7", lambda match: "." * len(match.group()), row)
within = False
# toggle on left
for c, ch in enumerate(row):
if ch in "|FL":
within = not within
if within:
inside.add((r, c))
total = len(inside - seen)
print(f"There are {total} tiles enclosed by the loop")
if __name__ == "__main__":
part1()
g, sol1 = part1()
part2(g, sol1)