101 lines
2.6 KiB
Python
101 lines
2.6 KiB
Python
import os
|
|
import sys
|
|
from typing import Literal, cast
|
|
|
|
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
|
|
|
Symbol = Literal["|", "-", "L", "J", "7", "F", ".", "S"]
|
|
|
|
lines: list[list[Symbol]] = [
|
|
[cast(Symbol, symbol) for symbol in line] for line in sys.stdin.read().splitlines()
|
|
]
|
|
|
|
# find starting point
|
|
si, sj = next(
|
|
(i, j)
|
|
for i in range(len(lines))
|
|
for j in range(len(lines[0]))
|
|
if lines[i][j] == "S"
|
|
)
|
|
|
|
# find one of the two outputs
|
|
ni, nj = si, sj
|
|
for ni, nj, chars in (
|
|
(si - 1, sj, "|7F"),
|
|
(si + 1, sj, "|LJ"),
|
|
(si, sj - 1, "-LF"),
|
|
(si, sj + 1, "-J7"),
|
|
):
|
|
if lines[ni][nj] in chars:
|
|
break
|
|
|
|
# part 1 - find the loop (re-used in part 2)
|
|
loop = [(si, sj), (ni, nj)]
|
|
while True:
|
|
pi, pj = loop[-2]
|
|
i, j = loop[-1]
|
|
|
|
sym = lines[i][j]
|
|
|
|
if sym == "|" and pi > i or sym in "JL" and pi == i:
|
|
i -= 1
|
|
elif sym == "|" and pi < i or sym in "7F" and pi == i:
|
|
i += 1
|
|
elif sym == "-" and pj > j or sym in "J7" and pj == j:
|
|
j -= 1
|
|
elif sym == "-" and pj < j or sym in "LF" and pj == j:
|
|
j += 1
|
|
|
|
if (i, j) == (si, sj):
|
|
break
|
|
|
|
loop.append((i, j))
|
|
|
|
answer_1 = len(loop) // 2
|
|
print(f"answer 1 is {answer_1}")
|
|
|
|
# part 2
|
|
|
|
# replace S by an appropriate character for the loop below
|
|
di1, dj1 = loop[1][0] - loop[0][0], loop[1][1] - loop[0][1]
|
|
di2, dj2 = loop[0][0] - loop[-1][0], loop[0][1] - loop[-1][1]
|
|
mapping: dict[tuple[int, int], dict[tuple[int, int], Symbol]] = {
|
|
(0, 1): {(0, 1): "-", (-1, 0): "F", (1, 0): "L"},
|
|
(0, -1): {(0, -1): "-", (-1, 0): "7", (1, 0): "J"},
|
|
(1, 0): {(1, 0): "|", (0, 1): "7", (0, -1): "F"},
|
|
(-1, 0): {(-1, 0): "|", (0, -1): "L", (0, 1): "J"},
|
|
}
|
|
lines[si][sj] = mapping[di1, dj1][di2, dj2]
|
|
|
|
# find the points inside the loop using an adaptation of ray casting for a discrete
|
|
# grid (https://en.wikipedia.org/wiki/Ray_casting)
|
|
#
|
|
# use a set for faster '... in loop' check
|
|
#
|
|
loop_s = set(loop)
|
|
inside: set[tuple[int, int]] = set()
|
|
for i in range(len(lines)):
|
|
cnt = 0
|
|
for j in range(len(lines[0])):
|
|
if (i, j) not in loop_s and cnt % 2 == 1:
|
|
inside.add((i, j))
|
|
|
|
if (i, j) in loop_s and lines[i][j] in "|LJ":
|
|
cnt += 1
|
|
|
|
if VERBOSE:
|
|
for i in range(len(lines)):
|
|
for j in range(len(lines[0])):
|
|
if (i, j) == (si, sj):
|
|
print("\033[91mS\033[0m", end="")
|
|
elif (i, j) in loop:
|
|
print(lines[i][j], end="")
|
|
elif (i, j) in inside:
|
|
print("\033[92mI\033[0m", end="")
|
|
else:
|
|
print(".", end="")
|
|
print()
|
|
|
|
answer_2 = len(inside)
|
|
print(f"answer 2 is {answer_2}")
|