day 08 solution
parent
5eea26dd13
commit
c43f19f2e3
|
@ -0,0 +1,157 @@
|
|||
use aoc_runner_derive::aoc;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[aoc(day8, part1)]
|
||||
fn part1(input: &str) -> u64 {
|
||||
parse(input).traverse()
|
||||
}
|
||||
|
||||
#[aoc(day8, part2)]
|
||||
fn part2(input: &str) -> u64 {
|
||||
parse(input).ghost_traverse()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const SAMPLE_DATA1: &'static str = r#"RL
|
||||
|
||||
AAA = (BBB, CCC)
|
||||
BBB = (DDD, EEE)
|
||||
CCC = (ZZZ, GGG)
|
||||
DDD = (DDD, DDD)
|
||||
EEE = (EEE, EEE)
|
||||
GGG = (GGG, GGG)
|
||||
ZZZ = (ZZZ, ZZZ)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn sample_data1() {
|
||||
assert_eq!(part1(&SAMPLE_DATA1), 2);
|
||||
}
|
||||
|
||||
const SAMPLE_DATA2: &'static str = r#"LLR
|
||||
|
||||
AAA = (BBB, BBB)
|
||||
BBB = (AAA, ZZZ)
|
||||
ZZZ = (ZZZ, ZZZ)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn sample_data2() {
|
||||
assert_eq!(part1(&SAMPLE_DATA2), 6);
|
||||
}
|
||||
|
||||
const SAMPLE_DATA_P2: &'static str = r#"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)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn sample_data_part2() {
|
||||
assert_eq!(part2(&SAMPLE_DATA_P2), 6);
|
||||
}
|
||||
}
|
|
@ -7,5 +7,6 @@ mod day04;
|
|||
mod day05;
|
||||
mod day06;
|
||||
mod day07;
|
||||
mod day08;
|
||||
|
||||
aoc_lib! { year = 2023 }
|
||||
|
|
Loading…
Reference in New Issue