use anyhow::{anyhow, Result}; use std::env::args; use std::fs::File; use std::io::{BufRead, BufReader}; use std::ops::RangeInclusive; use std::path::Path; use thiserror::Error; #[derive(Error, Debug)] enum BinarySpaceError { #[error("Space is not fully reduced")] NotReduced, } struct BinarySpace { start: usize, end: usize, } impl BinarySpace { fn new(range: RangeInclusive) -> BinarySpace // FIXME It would be nice with a cleaner interface. The intention was to use something like the // RangeBounds trait, but it seems to require support of Unbounded ranges, which this // BinarySpace will not be able to work with. { BinarySpace { start: *range.start(), end: *range.end(), } } fn size(&self) -> usize { self.end - self.start } fn split(&mut self, op: char) { let size = self.size() + 1; let half = size / 2; match op { 'F' | 'L' => self.end -= half, 'B' | 'R' => self.start += half, c => panic!("Invalid op to BinarySpace.split(): {}", c), } } fn value(&self) -> std::result::Result { if self.start == self.end { Ok(self.start) } else { Err(BinarySpaceError::NotReduced) } } } fn read_input>(filename: T) -> std::io::Result> { let f = File::open(filename)?; let reader = BufReader::new(f); reader.lines().collect() } fn parse_boarding_passes(input: &[String]) -> Result> { input.iter().map(|ops| { let mut row = BinarySpace::new(0..=127); let mut col = BinarySpace::new(0..=7); for op in ops.clone().drain(..) { match op { 'F' | 'B' => row.split(op), 'L' | 'R' => col.split(op), _ => return Err(anyhow!("Invalid op encountered")), } } Ok(row.value()? * 8 + col.value()?) }).collect() } fn part1(input: &[usize]) -> Option { input.iter().max().copied() } fn part2(input: &[usize]) -> Option { let mut ret = None; for seat in 1..(128*8) { if (input.contains(&seat), input.contains(&(seat-1)), input.contains(&(seat+1)) ) == (false, true, true) { match ret { None => ret = Some(seat), Some(_) => return None, /* No more than one solution allowed */ } } } ret } fn main() { let (do_part_1, do_part_2) = aoc::do_parts(); let filename = match args().nth(1) { Some(f) => f, None => { eprintln!("Missing input filename"); std::process::exit(1); }, }; match read_input(filename) { Ok(input) => { match parse_boarding_passes(&input) { Ok(seat_ids) => { if do_part_1 { match part1(&seat_ids) { Some(solution) => println!("Part1, highest seat ID found to be: {}", solution), None => eprintln!("Part1, no soluton found"), } } if do_part_2 { match part2(&seat_ids) { Some(solution) => println!("Part2, seat {} is lacking a boarding pass", solution), None => eprintln!("Part2, no soluton found"), } } }, Err(err) => eprintln!("Could not parse input: {}", err), } }, Err(err) => eprintln!("Could not read input: {}", err), } }