126 lines
3.1 KiB
Rust
126 lines
3.1 KiB
Rust
advent_of_code::solution!(8);
|
|
|
|
use num::integer::lcm;
|
|
use std::collections::{BTreeMap, HashSet};
|
|
|
|
type NodePath<'a> = BTreeMap<&'a str, &'a str>;
|
|
|
|
struct Path<'a> {
|
|
path: Vec<&'a str>,
|
|
left: NodePath<'a>,
|
|
right: NodePath<'a>,
|
|
}
|
|
|
|
fn parse(input: &str) -> Path {
|
|
let mut lines = input.lines();
|
|
let path = lines
|
|
.next()
|
|
.unwrap()
|
|
.split("")
|
|
.filter_map(|s| if s.is_empty() { None } else { Some(s) })
|
|
.collect::<Vec<&str>>();
|
|
|
|
let mut left: NodePath = BTreeMap::new();
|
|
let mut right: NodePath = BTreeMap::new();
|
|
|
|
for line in lines {
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
let parts1 = line.split(" = ").collect::<Vec<&str>>();
|
|
let src = parts1[0];
|
|
let parts2 = parts1[1]
|
|
.trim_start_matches("(")
|
|
.trim_end_matches(")")
|
|
.split(", ")
|
|
.collect::<Vec<&str>>();
|
|
|
|
left.insert(src, parts2[0]);
|
|
right.insert(src, parts2[1]);
|
|
}
|
|
|
|
Path { path, left, right }
|
|
}
|
|
|
|
impl<'a> Path<'a> {
|
|
fn traverse(&'a mut self) -> u64 {
|
|
let mut cur = "AAA";
|
|
let mut steps = 0;
|
|
|
|
for dir in self.path.iter().cycle() {
|
|
steps += 1;
|
|
match *dir {
|
|
"L" => cur = self.left.get(cur).unwrap(),
|
|
"R" => cur = self.right.get(cur).unwrap(),
|
|
n @ _ => panic!("Invalid character {:?}!", n),
|
|
}
|
|
if cur == "ZZZ" {
|
|
break;
|
|
}
|
|
}
|
|
|
|
steps
|
|
}
|
|
|
|
fn ghost_traverse(&'a mut self) -> u64 {
|
|
let mut unique: HashSet<&str> = HashSet::new();
|
|
for (src, _dest) in &self.left {
|
|
if src.ends_with("A") {
|
|
unique.insert(src);
|
|
}
|
|
}
|
|
for (src, _dest) in &self.right {
|
|
if src.ends_with("A") {
|
|
unique.insert(src);
|
|
}
|
|
}
|
|
let mut steps = 1;
|
|
|
|
for ghost in unique.into_iter() {
|
|
let mut gsteps = 0;
|
|
let mut cur = ghost;
|
|
for dir in self.path.iter().cycle() {
|
|
gsteps += 1;
|
|
match *dir {
|
|
"L" => cur = self.left.get(cur).unwrap(),
|
|
"R" => cur = self.right.get(cur).unwrap(),
|
|
n @ _ => panic!("Invalid path character {:?}!", n),
|
|
}
|
|
if cur.ends_with("Z") {
|
|
steps = lcm(gsteps, steps);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
steps
|
|
}
|
|
}
|
|
|
|
pub fn part_one(input: &str) -> Option<u64> {
|
|
Some(parse(input).traverse())
|
|
}
|
|
|
|
pub fn part_two(input: &str) -> Option<u64> {
|
|
Some(parse(input).ghost_traverse())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_part_one() {
|
|
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
|
|
assert_eq!(result, Some(2));
|
|
}
|
|
|
|
#[test]
|
|
fn test_part_two() {
|
|
let result = part_two(&advent_of_code::template::read_file_part(
|
|
"examples", DAY, 2,
|
|
));
|
|
assert_eq!(result, Some(6));
|
|
}
|
|
}
|