use { anyhow::{ anyhow, Context, Result, }, regex::{ Regex, }, std::{ collections::HashMap, env::args, fs::File, io::{ BufRead, BufReader, }, iter::repeat, path::Path, }, }; #[derive(Debug,Eq,Hash,PartialEq)] struct Coord { x: usize, y: usize, } #[derive(Debug)] struct Line { start: Coord, end: Coord, } impl Line { fn coords(&self) -> Vec { let (iter_x, iter_y): (Box>, Box>) = ( match (self.start.x, self.end.x) { (start, end) if start < end => Box::new(start..=end), (start, end) if start > end => Box::new((end..=start).rev()), (start, _end) /* equals */ => Box::new(repeat(start)), }, match (self.start.y, self.end.y) { (start, end) if start < end => Box::new(start..=end), (start, end) if start > end => Box::new((end..=start).rev()), (start, _end) /* equals */ => Box::new(repeat(start)), }, ); iter_x.zip(iter_y).map(|(x, y)| Coord{x, y}).collect() } } fn read_input>(filename: T) -> Result> { let reader = BufReader::new(File::open(filename)?); let re = Regex::new(r#"(?x) (?P[0-9]+), (?P[0-9]+)[[:blank:]]*->[[:blank:]]* (?P[0-9]+), (?P[0-9]+) "#)?; reader.lines().map(|row| { let string = row?; let caps = re.captures(&string).ok_or_else(|| anyhow!("Could not parse: {}", string))?; match (caps.name("x1"), caps.name("y1"), caps.name("x2"), caps.name("y2")) { (Some(x1), Some(y1), Some(x2), Some(y2)) => { let x = x1.as_str().parse()?; let y = y1.as_str().parse()?; let start = Coord{x, y}; let x = x2.as_str().parse()?; let y = y2.as_str().parse()?; let end = Coord {x, y}; Ok(Line{start, end}) }, _ => Err(anyhow!("Could not parse: {}", string)), } }).collect() } fn count_overlaps(points: &HashMap) -> Result{ let mut overlap = 0; let min_x = points.keys().map(|coord| coord.x).min().ok_or(anyhow!("Problem finding min x"))?; let max_x = points.keys().map(|coord| coord.x).max().ok_or(anyhow!("Problem finding max x"))?; let min_y = points.keys().map(|coord| coord.y).min().ok_or(anyhow!("Problem finding min y"))?; let max_y = points.keys().map(|coord| coord.y).max().ok_or(anyhow!("Problem finding max y"))?; for y in min_y..=max_y { for x in min_x..=max_x { if let Some(v) = points.get(&Coord {x, y}) { if *v >= 2 { overlap += 1; } } } } Ok(overlap) } fn part1<'a, I: IntoIterator>(input: I) -> Result { let mut points = HashMap::new(); for line in input.into_iter().filter(|l| l.start.x == l.end.x || l.start.y == l.end.y) { for coord in line.coords() { let point = points.entry(coord).or_insert(0); *point += 1; } } count_overlaps(&points) } fn part2<'a, I: IntoIterator>(input: I) -> Result { let mut points = HashMap::new(); for line in input.into_iter() { for coord in line.coords() { let point = points.entry(coord).or_insert(0); *point += 1; } } count_overlaps(&points) } 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(()) }