222 lines
5.6 KiB
Python
222 lines
5.6 KiB
Python
# --- Day 12: Passage Pathing ---
|
|
|
|
# With your submarine's subterranean subsystems subsisting suboptimally, the
|
|
# only way you're getting out of this cave anytime soon is by finding a path
|
|
# yourself. Not just a path - the only way to know if you've found the best
|
|
# path is to find all of them.
|
|
|
|
# Fortunately, the sensors are still mostly working, and so you build a rough
|
|
# map of the remaining caves (your puzzle input). For example:
|
|
|
|
# start-A
|
|
# start-b
|
|
# A-c
|
|
# A-b
|
|
# b-d
|
|
# A-end
|
|
# b-end
|
|
|
|
# This is a list of how all of the caves are connected. You start in the cave
|
|
# named start, and your destination is the cave named end. An entry like b-d
|
|
# means that cave b is connected to cave d - that is, you can move between
|
|
# them.
|
|
|
|
# So, the above cave system looks roughly like this:
|
|
|
|
# start
|
|
# / \
|
|
# c--A-----b--d
|
|
# \ /
|
|
# end
|
|
|
|
# Your goal is to find the number of distinct paths that start at start, end at
|
|
# end, and don't visit small caves more than once. There are two types of
|
|
# caves: big caves (written in uppercase, like A) and small caves (written in
|
|
# lowercase, like b). It would be a waste of time to visit any small cave more
|
|
# than once, but big caves are large enough that it might be worth visiting
|
|
# them multiple times. So, all paths you find should visit small caves at most
|
|
# once, and can visit big caves any number of times.
|
|
|
|
# Given these rules, there are 10 paths through this example cave system:
|
|
|
|
# start,A,b,A,c,A,end
|
|
# start,A,b,A,end
|
|
# start,A,b,end
|
|
# start,A,c,A,b,A,end
|
|
# start,A,c,A,b,end
|
|
# start,A,c,A,end
|
|
# start,A,end
|
|
# start,b,A,c,A,end
|
|
# start,b,A,end
|
|
# start,b,end
|
|
|
|
# (Each line in the above list corresponds to a single path; the caves visited
|
|
# by that path are listed in the order they are visited and separated by
|
|
# commas.)
|
|
|
|
# Note that in this cave system, cave d is never visited by any path: to do so,
|
|
# cave b would need to be visited twice (once on the way to cave d and a second
|
|
# time when returning from cave d), and since cave b is small, this is not
|
|
# allowed.
|
|
|
|
# Here is a slightly larger example:
|
|
|
|
# dc-end
|
|
# HN-start
|
|
# start-kj
|
|
# dc-start
|
|
# dc-HN
|
|
# LN-dc
|
|
# HN-end
|
|
# kj-sa
|
|
# kj-HN
|
|
# kj-dc
|
|
|
|
# The 19 paths through it are as follows:
|
|
|
|
# start,HN,dc,HN,end
|
|
# start,HN,dc,HN,kj,HN,end
|
|
# start,HN,dc,end
|
|
# start,HN,dc,kj,HN,end
|
|
# start,HN,end
|
|
# start,HN,kj,HN,dc,HN,end
|
|
# start,HN,kj,HN,dc,end
|
|
# start,HN,kj,HN,end
|
|
# start,HN,kj,dc,HN,end
|
|
# start,HN,kj,dc,end
|
|
# start,dc,HN,end
|
|
# start,dc,HN,kj,HN,end
|
|
# start,dc,end
|
|
# start,dc,kj,HN,end
|
|
# start,kj,HN,dc,HN,end
|
|
# start,kj,HN,dc,end
|
|
# start,kj,HN,end
|
|
# start,kj,dc,HN,end
|
|
# start,kj,dc,end
|
|
|
|
# Finally, this even larger example has 226 paths through it:
|
|
|
|
# fs-end
|
|
# he-DX
|
|
# fs-he
|
|
# start-DX
|
|
# pj-DX
|
|
# end-zg
|
|
# zg-sl
|
|
# zg-pj
|
|
# pj-he
|
|
# RW-he
|
|
# fs-DX
|
|
# pj-RW
|
|
# zg-RW
|
|
# start-pj
|
|
# he-WI
|
|
# zg-he
|
|
# pj-fs
|
|
# start-RW
|
|
|
|
# How many paths through this cave system are there that visit small caves at
|
|
# most once?
|
|
|
|
from collections import defaultdict
|
|
from typing import DefaultDict
|
|
|
|
caves_map = defaultdict(list)
|
|
with open("files/P12.txt") as f:
|
|
for row in f:
|
|
start, end = row.strip().split("-")
|
|
caves_map[start].append(end)
|
|
caves_map[end].append(start)
|
|
|
|
|
|
def dfs(
|
|
node: str,
|
|
graph: DefaultDict[str, list[str]],
|
|
visited: set[str],
|
|
already_visited: bool,
|
|
counter: int = 0,
|
|
):
|
|
if node == "end":
|
|
return 1
|
|
for neighbour in graph[node]:
|
|
if neighbour.isupper():
|
|
counter += dfs(neighbour, graph, visited, already_visited)
|
|
else:
|
|
if neighbour not in visited:
|
|
counter += dfs(
|
|
neighbour, graph, visited | {neighbour}, already_visited
|
|
)
|
|
elif already_visited and neighbour not in {"start", "end"}:
|
|
counter += dfs(neighbour, graph, visited, False)
|
|
return counter
|
|
|
|
|
|
def part_1() -> None:
|
|
ans = dfs("start", caves_map, {"start"}, already_visited=False)
|
|
print(f"There are {ans} paths visiting small caves")
|
|
|
|
|
|
# --- Part Two ---
|
|
|
|
# After reviewing the available paths, you realize you might have time to visit
|
|
# a single small cave twice. Specifically, big caves can be visited any number
|
|
# of times, a single small cave can be visited at most twice, and the remaining
|
|
# small caves can be visited at most once. However, the caves named start and
|
|
# end can only be visited exactly once each: once you leave the start cave, you
|
|
# may not return to it, and once you reach the end cave, the path must end
|
|
# immediately.
|
|
|
|
# Now, the 36 possible paths through the first example above are:
|
|
|
|
# start,A,b,A,b,A,c,A,end
|
|
# start,A,b,A,b,A,end
|
|
# start,A,b,A,b,end
|
|
# start,A,b,A,c,A,b,A,end
|
|
# start,A,b,A,c,A,b,end
|
|
# start,A,b,A,c,A,c,A,end
|
|
# start,A,b,A,c,A,end
|
|
# start,A,b,A,end
|
|
# start,A,b,d,b,A,c,A,end
|
|
# start,A,b,d,b,A,end
|
|
# start,A,b,d,b,end
|
|
# start,A,b,end
|
|
# start,A,c,A,b,A,b,A,end
|
|
# start,A,c,A,b,A,b,end
|
|
# start,A,c,A,b,A,c,A,end
|
|
# start,A,c,A,b,A,end
|
|
# start,A,c,A,b,d,b,A,end
|
|
# start,A,c,A,b,d,b,end
|
|
# start,A,c,A,b,end
|
|
# start,A,c,A,c,A,b,A,end
|
|
# start,A,c,A,c,A,b,end
|
|
# start,A,c,A,c,A,end
|
|
# start,A,c,A,end
|
|
# start,A,end
|
|
# start,b,A,b,A,c,A,end
|
|
# start,b,A,b,A,end
|
|
# start,b,A,b,end
|
|
# start,b,A,c,A,b,A,end
|
|
# start,b,A,c,A,b,end
|
|
# start,b,A,c,A,c,A,end
|
|
# start,b,A,c,A,end
|
|
# start,b,A,end
|
|
# start,b,d,b,A,c,A,end
|
|
# start,b,d,b,A,end
|
|
# start,b,d,b,end
|
|
# start,b,end
|
|
|
|
# The slightly larger example above now has 103 paths through it, and the even
|
|
# larger example now has 3509 paths through it.
|
|
|
|
# Given these new rules, how many paths through this cave system are there?
|
|
|
|
|
|
def part_2() -> None:
|
|
ans = dfs("start", caves_map, {"start"}, already_visited=True)
|
|
print(f"There are {ans} paths in total")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
part_1()
|
|
part_2()
|