use { anyhow::{ anyhow, Context, Result, }, std::{ collections::BTreeSet, env::args, fmt::{ Display, Error as FmtError, Formatter, Result as FmtResult, }, fs::File, io::{ BufRead, BufReader, }, path::Path, }, }; #[derive(Clone,Copy,Debug,Eq,Ord,PartialEq,PartialOrd)] struct Point { y: usize, // Please note that ordering is derived with y prior to x, as required for the x: usize, // Display trait to work as currently implemented. } impl Point { fn fold(&self, fold: Fold) -> Self { match fold { Fold::X(num) => { if self.x > num { return Point{x: num - (self.x - num), y: self.y}; } } Fold::Y(num) => { if self.y > num { return Point{x: self.x, y: num-(self.y - num)}; } } } *self } } #[derive(Clone,Copy)] enum Fold { X(usize), Y(usize), } struct Paper { dots: BTreeSet, } impl Paper { fn fold(&self, fold: &Fold) -> Result { let mut folded = Paper { dots: BTreeSet::new(), }; for point in &self.dots { let mirror = point.fold(*fold); if self.dots.contains(point) { folded.dots.insert(mirror); } } Ok(folded) } fn dot_count(&self) -> usize { self.dots.len() } } impl Display for Paper { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { let mut s = String::new(); let (mut x, mut y) = (0, 0); let max_x = self.dots.iter().map(|p| p.x).max().ok_or(FmtError)?; for p in self.dots.iter() { if p.y == y { (x..p.x).for_each(|_| s += "." ); } else { (x..=max_x).for_each(|_| s += "." ); s += "\n"; for _ in y..(p.y-1) { (0..=max_x).for_each(|_| s += "." ); s += "\n"; } (0..p.x).for_each(|_| s += "." ); } s += "#"; x = p.x+1; y = p.y; if x > max_x { x = 0; y += 1; s += "\n"; } } (x..=max_x).for_each(|_| s += "." ); write!(f, "{}", s) } } fn read_input>(filename: T) -> Result<(Paper, Vec)> { let reader = BufReader::new(File::open(filename)?); let mut lines = reader.lines(); let mut dots: BTreeSet = BTreeSet::new(); for line in lines.by_ref() { let l = line.map_err(|err| anyhow!("Failed reading from file: {}", err))?; if l.is_empty() { break; } let values: Vec<_> = l.split(',') .map(|v| v.parse().map_err(|err| anyhow!("{}: {}", l, err))).collect::>()?; if values.len() != 2 { return Err(anyhow!("Failed parsing coordinates from line: {}", l)); } let (x, y) = match (values.get(0), values.get(1)) { (Some(parsed_x), Some(parsed_y)) => Ok((parsed_x, parsed_y)), (_, _) => Err(anyhow!("Invalid coordinates: {}", l)), }?; dots.insert(Point{x: *x, y: *y}); } let folds = lines.map(|line| { let l = line?; let s = l.strip_prefix("fold along ") .ok_or_else(|| anyhow!("Could not find fold: {}", l))?; let (axis, position) = s.split_once('=') .ok_or_else(|| anyhow!("Could not parse fold: {}", l))?; let num = position.parse() .map_err(|err| anyhow!("Invalid fold position: {}, {}", l, err))?; let fold = match axis { "x" => Ok(Fold::X(num)), "y" => Ok(Fold::Y(num)), _ => Err(anyhow!("Invalid fold axis: {}", axis)), }?; Ok(fold) }).collect::>>()?; Ok((Paper { dots, }, folds)) } fn part1(paper: &Paper, folds: &[Fold]) -> Result { let fold = folds.get(0).ok_or_else(|| anyhow!("Could not find any fold"))?; let folded = paper.fold(fold)?; Ok(folded.dot_count()) } fn part2(paper: Paper, folds: &[Fold]) -> Result { let folded = folds.iter().fold(Ok(paper), |p: Result, fold| { let folded = p?.fold(fold)?; Ok(folded) })?; Ok(format!("{}", folded)) } 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 (paper, folds) = read_input(filename).context("Could not read input")?; if do_part_1 { let solution = part1(&paper, &folds).context("No solution for part 1")?; println!("Part1, solution found to be: {}", solution); } if do_part_2 { let solution = part2(paper, &folds).context("No solution for part 2")?; println!("Part2, solution found to be:\n{}", solution); } Ok(()) }