2023-12-08 13:00:30 +00:00
|
|
|
import re
|
2023-12-08 13:24:07 +00:00
|
|
|
import math
|
2023-12-08 13:00:30 +00:00
|
|
|
from itertools import cycle
|
|
|
|
|
|
|
|
def parse_input(infile):
|
|
|
|
with open(infile) as f:
|
|
|
|
content = f.read().rstrip()
|
|
|
|
directions, nodes = content.split("\n\n")
|
|
|
|
directions = directions.strip()
|
|
|
|
nodes = nodes.split("\n")
|
|
|
|
nodes = [n.split(" = ") for n in nodes]
|
|
|
|
nodes = {k: re.findall(r"\w{3}", v) for k, v in nodes}
|
|
|
|
return directions, nodes
|
|
|
|
|
|
|
|
|
|
|
|
def part1(directions, nodes):
|
|
|
|
iterations = 0
|
|
|
|
current_node = "AAA"
|
|
|
|
for d in cycle(directions):
|
|
|
|
if current_node == "ZZZ":
|
|
|
|
break
|
|
|
|
iterations += 1
|
|
|
|
if d == "L":
|
|
|
|
current_node = nodes[current_node][0]
|
|
|
|
else:
|
|
|
|
current_node = nodes[current_node][1]
|
2023-12-08 13:24:07 +00:00
|
|
|
print(f"Part 1: reached 'ZZZ' in {iterations} iterations")
|
2023-12-08 13:00:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
def part2(directions, nodes):
|
|
|
|
current_nodes = [k for k in nodes.keys() if k.endswith("A")]
|
2023-12-08 13:24:07 +00:00
|
|
|
# keep track of iterations number for each visited node
|
|
|
|
# (the number will stop to beeing incremented once the node n_i value reached the target value 'xxZ')
|
2023-12-08 13:00:30 +00:00
|
|
|
iterations = [0] * len(current_nodes)
|
|
|
|
|
|
|
|
for d in cycle(directions):
|
|
|
|
if all(c.endswith("Z") for c in current_nodes):
|
|
|
|
break
|
2023-12-08 13:24:07 +00:00
|
|
|
|
2023-12-08 13:00:30 +00:00
|
|
|
if d == "L":
|
|
|
|
new_nodes = []
|
|
|
|
for i, n in enumerate(current_nodes):
|
2023-12-08 13:24:07 +00:00
|
|
|
if n.endswith("Z"): # end condition already reached for this node
|
|
|
|
new_nodes.append(n)
|
|
|
|
else:
|
2023-12-08 13:00:30 +00:00
|
|
|
new_nodes.append(nodes[n][0])
|
|
|
|
iterations[i] += 1
|
|
|
|
current_nodes = new_nodes
|
|
|
|
else:
|
|
|
|
new_nodes = []
|
|
|
|
for i, n in enumerate(current_nodes):
|
2023-12-08 13:24:07 +00:00
|
|
|
if n.endswith("Z"): # end condition already reached for this node
|
|
|
|
new_nodes.append(n)
|
|
|
|
else:
|
2023-12-08 13:00:30 +00:00
|
|
|
new_nodes.append(nodes[n][1])
|
|
|
|
iterations[i] += 1
|
|
|
|
current_nodes = new_nodes
|
2023-12-08 13:24:07 +00:00
|
|
|
|
|
|
|
# the result is the lowest common multiple between the number of iterations
|
|
|
|
# for each node
|
|
|
|
result = math.lcm(*iterations)
|
|
|
|
print(f"Part 2: reached all nodes such that 'xxZ' in {result} iterations")
|
2023-12-08 13:00:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
SCRIPTPATH = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
infile = sys.argv[1] if len(sys.argv) == 2 else "example.txt"
|
|
|
|
|
|
|
|
directions, nodes = parse_input(os.path.join(SCRIPTPATH, infile))
|
|
|
|
part1(directions, nodes)
|
|
|
|
|
|
|
|
directions, nodes = parse_input(os.path.join(SCRIPTPATH, infile))
|
|
|
|
part2(directions, nodes)
|