use { anyhow::{ anyhow, Context, Result, }, std::{ env::args, fmt::{ Display, Formatter, Result as FmtResult, }, fs::File, io::{ BufRead, BufReader, }, path::Path, }, }; #[derive(Copy,Clone,Eq,PartialEq)] enum Bracket { RoundOpen, RoundClose, SquareOpen, SquareClose, WigglyOpen, WigglyClose, AngleOpen, AngleClose, } impl Display for Bracket { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { let c = match &self { Bracket::RoundOpen => '(', Bracket::RoundClose => ')', Bracket::SquareOpen => '[', Bracket::SquareClose => ']', Bracket::WigglyOpen => '{', Bracket::WigglyClose => '}', Bracket::AngleOpen => '<', Bracket::AngleClose => '>', }; write!(f, "{}", c) } } struct BracketLine { inner: Vec, } impl BracketLine { fn new(init: I) -> Self where I: IntoIterator { let inner = init.into_iter().collect(); Self { inner, } } fn is_corrupt(&self) -> bool { let mut stack = vec![]; for c in &self.inner { match c { Bracket::RoundOpen | Bracket::SquareOpen | Bracket::WigglyOpen | Bracket::AngleOpen => stack.push(c), Bracket::RoundClose => if stack.pop() != Some(&Bracket::RoundOpen) { return true; }, Bracket::SquareClose => if stack.pop() != Some(&Bracket::SquareOpen) { return true; }, Bracket::WigglyClose => if stack.pop() != Some(&Bracket::WigglyOpen) { return true; }, Bracket::AngleClose => if stack.pop() != Some(&Bracket::AngleOpen) { return true; }, } } stack.is_empty() } fn check_corrupt(&self) -> Option { let mut stack = vec![]; for c in &self.inner { match c { Bracket::RoundOpen | Bracket::SquareOpen | Bracket::WigglyOpen | Bracket::AngleOpen => stack.push(c), Bracket::RoundClose => if stack.pop() != Some(&Bracket::RoundOpen) { return Some(3); }, Bracket::SquareClose => if stack.pop() != Some(&Bracket::SquareOpen) { return Some(57); }, Bracket::WigglyClose => if stack.pop() != Some(&Bracket::WigglyOpen) { return Some(1197); }, Bracket::AngleClose => if stack.pop() != Some(&Bracket::AngleOpen) { return Some(25137); }, } } None } fn fix_incomplete(&self) -> Option { let mut stack = vec![]; let mut points = 0; for c in &self.inner { match c { Bracket::RoundOpen | Bracket::SquareOpen | Bracket::WigglyOpen | Bracket::AngleOpen => stack.push(*c), Bracket::RoundClose => if stack.pop() != Some(Bracket::RoundOpen) { return None; }, Bracket::SquareClose => if stack.pop() != Some(Bracket::SquareOpen) { return None; }, Bracket::WigglyClose => if stack.pop() != Some(Bracket::WigglyOpen) { return None; }, Bracket::AngleClose => if stack.pop() != Some(Bracket::AngleOpen) { return None; }, } } for c in stack.into_iter().rev() { match c { Bracket::RoundOpen => { points *= 5; points += 1; // FIXME A real fixer should actually add the missing brackets, but this // implementation only cares about collecting the points. // self.inner.push(Bracket::RoundClose); }, Bracket::SquareOpen => { points *= 5; points += 2; // self.inner.push(Bracket::SquareClose); }, Bracket::WigglyOpen => { points *= 5; points += 3; // self.inner.push(Bracket::WigglyClose); }, Bracket::AngleOpen => { points *= 5; points += 4; // self.inner.push(Bracket::AngleClose); }, Bracket::RoundClose | Bracket::SquareClose | Bracket::WigglyClose | Bracket::AngleClose => return None, } } Some(points) } } fn read_input>(filename: T) -> Result> { let reader = BufReader::new(File::open(filename)?); reader.lines().map( |v| { let s = v?; let bracket_row = s.chars().map(|c| match c { '(' => Ok(Bracket::RoundOpen), ')' => Ok(Bracket::RoundClose), '[' => Ok(Bracket::SquareOpen), ']' => Ok(Bracket::SquareClose), '{' => Ok(Bracket::WigglyOpen), '}' => Ok(Bracket::WigglyClose), '<' => Ok(Bracket::AngleOpen), '>' => Ok(Bracket::AngleClose), invalid => Err(anyhow!("Invalid input: {}", invalid)), }).collect::>>()?; Ok(BracketLine::new(bracket_row)) } ).collect() } fn part1<'a, I>(input: I) -> Result where I: IntoIterator { let mut points = 0; for line in input { if let Some(val) = line.check_corrupt() { points += val; } } Ok(points) } fn part2<'a, I>(input: I) -> Result where I: IntoIterator { let mut points = input.into_iter() .filter(|line| !line.is_corrupt()) .filter_map(|line| line.fix_incomplete()).collect::>(); points.sort_unstable(); let contestant_count = &points.len(); let winner = points.into_iter().nth(contestant_count/2) .ok_or(anyhow!("Unable to find winner"))?; Ok(winner) } fn main() -> Result<()> { let ( do_part_1, do_part_2 ) = aoc::do_parts(); let filename = args().nth(1).ok_or(anyhow!("Missing input filename"))?; let input = read_input(filename).context("Could not read input")?; if do_part_1 { let solution = part1(&input).context("No solution for part 1")?; println!("Part1, solution found to be: {}", solution); } if do_part_2 { let solution = part2(&input).context("No solution for part 2")?; println!("Part2, solution found to be: {}", solution); } Ok(()) }