use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; use lazy_regex::{lazy_regex, Lazy, Regex}; // BOILERPLATE type InputIter = Lines>; fn get_input() -> InputIter { let f = File::open("input").unwrap(); let br = BufReader::new(f); br.lines() } fn main() { println!("Problem 1 solution: {}", problem1(get_input())); println!("Problem 2 solution: {}", problem2(get_input())); } // PARSE const LINE_REGEX: Lazy = lazy_regex!(r"([A-Z0-9]{3}) = \(([A-Z0-9]{3}), ([A-Z0-9]{3})\)"); enum Instruction { LEFT, RIGHT, } impl From for Instruction { fn from(c: char) -> Self { match c { 'L' => Self::LEFT, 'R' => Self::RIGHT, _ => panic!("Invalid instruction"), } } } #[derive(Debug)] struct Element { left: String, right: String, } type Nodes = HashMap; #[derive(Debug)] struct Map { instructions: Vec, nodes: Nodes, } fn parse_node(nodes: &mut Nodes, node: &str) { let matches = LINE_REGEX.captures(node).unwrap(); let node_id = matches.get(1).unwrap().as_str(); let left = matches.get(2).unwrap().as_str(); let right = matches.get(3).unwrap().as_str(); nodes.insert( node_id.to_string(), Element { left: left.to_string(), right: right.to_string(), }, ); } impl From> for Map { fn from(mut lines: Lines) -> Self { let instructions = lines.next().unwrap().unwrap().chars().collect(); let mut nodes = HashMap::new(); lines.next(); for line in lines { parse_node(&mut nodes, line.unwrap().as_str()); } Self { instructions, nodes, } } } // PROBLEM 1 solution fn problem1(input: Lines) -> u64 { let map = Map::from(input); let mut count = 0; let mut cur_node = "AAA"; for side in map.instructions.iter().cycle() { count += 1; cur_node = match side { 'L' => &map.nodes[cur_node].left, 'R' => &map.nodes[cur_node].right, _ => panic!("invalid instruction"), }; if cur_node == "ZZZ" { return count; } } 0 } fn node_cycle_length(map: &Map, node: &str) -> u64 { let mut count = 0; let mut cur_node = node; for side in map.instructions.iter().cycle() { count += 1; cur_node = match side { 'L' => &map.nodes[cur_node].left, 'R' => &map.nodes[cur_node].right, _ => panic!("invalid instruction"), }; if cur_node.ends_with('Z') { return count; } } 0 } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { let map = Map::from(input); let starting_nodes: Vec<_> = map.nodes.keys().filter(|s| s.ends_with('A')).collect(); starting_nodes.iter().map(|n| node_cycle_length(&map, *n)).fold(1, |accum, elem| num::integer::lcm(accum, elem)) } #[cfg(test)] mod tests { use crate::*; use std::io::Cursor; const EXAMPLE: &str = &"RL AAA = (BBB, CCC) BBB = (DDD, EEE) CCC = (ZZZ, GGG) DDD = (DDD, DDD) EEE = (EEE, EEE) GGG = (GGG, GGG) ZZZ = (ZZZ, ZZZ)"; const EXAMPLE2: &str = &"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 problem1_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem1(c.lines()), 2); } #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE2); assert_eq!(problem2(c.lines()), 6); } }