use crate::day_8::Instruction::{Left, Right};
use num::Integer;
use std::collections::HashMap;
use std::fs;
#[derive(Eq, PartialEq, Debug)]
enum Instruction {
Left,
Right,
}
impl TryFrom<char> for Instruction {
type Error = ();
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'L' => Ok(Left),
'R' => Ok(Right),
_ => Err(()),
}
}
}
type Node<'a> = (&'a str, &'a str);
type Network<'a> = HashMap<&'a str, Node<'a>>;
pub fn run() {
let contents = fs::read_to_string("res/day-8-input.txt").expect("Failed to read file");
let (instructions, network) = parse_input(&contents);
println!(
"The number of steps is: {}",
count_steps("AAA", part_1_terminal, &instructions, &network)
);
println!(
"The number of ghost steps is: {}",
count_parallel_steps(&instructions, &network)
);
}
fn parse_input(input: &String) -> (Vec<Instruction>, Network) {
let (instructions_spec, network_spec) = input.split_once("\n\n").unwrap();
(
parse_instructions(instructions_spec),
parse_network(network_spec),
)
}
fn parse_instructions(line: &str) -> Vec<Instruction> {
line.chars().filter_map(|c| c.try_into().ok()).collect()
}
fn parse_network(network_spec: &str) -> Network {
network_spec.lines().map(parse_node).collect()
}
fn parse_node(node_spec: &str) -> (&str, Node) {
let (label, connections) = node_spec.split_once(" = ").unwrap();
let (left, right) = connections.split_once(", ").unwrap();
(
label,
(left.trim_start_matches("("), right.trim_end_matches(")")),
)
}
fn part_1_terminal(position: &str) -> bool {
position == "ZZZ"
}
fn part_2_terminal(position: &str) -> bool {
position.ends_with("Z")
}
fn count_steps(
start: &str,
terminal_predicate: fn(&str) -> bool,
instructions: &Vec<Instruction>,
network: &Network,
) -> usize {
let mut steps = 0;
let mut position = start;
let instruction_length = instructions.len();
while !terminal_predicate(position) {
let direction = instructions.get(steps % instruction_length).unwrap();
let &(left, right) = network.get(position).unwrap();
position = if *direction == Left { left } else { right };
steps += 1;
}
steps
}
fn count_parallel_steps(instructions: &Vec<Instruction>, network: &Network) -> usize {
network
.keys()
.filter(|k| k.ends_with("A"))
.map(|&start| count_steps(start, part_2_terminal, instructions, network))
.fold(1, |acc, steps| steps.lcm(&acc))
}
#[cfg(test)]
mod tests {
use crate::day_8::*;
fn example_networks() -> Vec<Network<'static>> {
vec![
vec![
("AAA", ("BBB", "CCC")),
("BBB", ("DDD", "EEE")),
("CCC", ("ZZZ", "GGG")),
("DDD", ("DDD", "DDD")),
("EEE", ("EEE", "EEE")),
("GGG", ("GGG", "GGG")),
("ZZZ", ("ZZZ", "ZZZ")),
]
.into_iter()
.collect(),
vec![
("AAA", ("BBB", "BBB")),
("BBB", ("AAA", "ZZZ")),
("ZZZ", ("ZZZ", "ZZZ")),
]
.into_iter()
.collect(),
]
}
#[test]
fn can_parse_input() {
let input_0 = "\
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)"
.to_string();
let input_1 = "\
LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)"
.to_string();
let networks = example_networks();
let (instructions_0, network_0) = parse_input(&input_0);
assert_eq!(instructions_0, vec![Right, Left]);
assert_eq!(network_0, networks[0]);
let (instructions_1, network_1) = parse_input(&input_1);
assert_eq!(instructions_1, vec![Left, Left, Right]);
assert_eq!(network_1, networks[1]);
}
#[test]
fn can_count_steps() {
let networks = example_networks();
assert_eq!(
count_steps("AAA", part_1_terminal, &vec![Right, Left], &networks[0]),
2
);
assert_eq!(
count_steps(
"AAA",
part_1_terminal,
&vec![Left, Left, Right],
&networks[1]
),
6
);
}
#[test]
fn can_count_parallel_steps() {
let input = "\
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)"
.to_string();
let (instructions, network) = parse_input(&input);
assert_eq!(count_parallel_steps(&instructions, &network), 6);
}
}