type Error = Box; #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)] pub enum Move { Right(u32), Left(u32), Up(u32), Down(u32), } #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)] pub enum Segment { Horizontal { row: i32, col_start: i32, col_end: i32, steps_start: i32, steps_dir: i32, }, Vertical { col: i32, row_start: i32, row_end: i32, steps_start: i32, steps_dir: i32, }, } impl Segment { pub fn collision(&self, other: &Self) -> Option { match (*self, *other) { (Segment::Horizontal {..}, Segment::Horizontal {..}) => None, (Segment::Vertical {..}, Segment::Vertical {..}) => None, ( Segment::Horizontal { row, col_start, col_end, steps_start: col_steps_start, steps_dir: col_steps_dir }, Segment::Vertical { col, row_start, row_end, steps_start: row_steps_start, steps_dir: row_steps_dir }, ) |( Segment::Vertical { col, row_start, row_end, steps_start: row_steps_start, steps_dir: row_steps_dir }, Segment::Horizontal { row, col_start, col_end, steps_start: col_steps_start, steps_dir: col_steps_dir }, ) => { if row_start <= row && row <= row_end && col_start <= col && col <= col_end { let col_steps = col_steps_start + (col - col_start) * col_steps_dir; let row_steps = row_steps_start + (row - row_start) * row_steps_dir; Some(Position { row, col, steps: col_steps + row_steps }) } else { None } } } } } #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default)] pub struct Position { row: i32, col: i32, steps: i32, } impl Position { pub fn manhatten_distance(&self) -> u32 { self.row.abs() as u32 + self.col.abs() as u32 } pub fn trace(&mut self, mov: Move) -> Segment { let start = *self; match mov { Move::Right(off) => { self.col += off as i32; self.steps += off as i32; Segment::Horizontal { row: start.row, col_start: start.col, col_end: self.col, steps_start: start.steps, steps_dir: 1, } }, Move::Left(off) => { self.col -= off as i32; self.steps += off as i32; Segment::Horizontal { row: start.row, col_start: self.col, col_end: start.col, steps_start: self.steps, steps_dir: -1, } }, Move::Up(off) => { self.row -= off as i32; self.steps += off as i32; Segment::Vertical { col: start.col, row_start: self.row, row_end: start.row, steps_start: self.steps, steps_dir: -1, } }, Move::Down(off) => { self.row += off as i32; self.steps += off as i32; Segment::Vertical { col: start.col, row_start: start.row, row_end: self.row, steps_start: start.steps, steps_dir: 1, } }, } } } impl std::str::FromStr for Move { type Err = Error; fn from_str(s: &str) -> Result { let e = move || Box::new(std::io::Error::new(std::io::ErrorKind::Other, "invalid move")); let n = s[1..].parse::()?; if s.is_empty() || !s.is_ascii() { return Err(e()); } Ok(match s.as_bytes()[0] { b'R' => Move::Right(n), b'L' => Move::Left(n), b'U' => Move::Up(n), b'D' => Move::Down(n), _ => return Err(e()), }) } } fn parse_moves<'a>(input: &'a str) -> impl Iterator> + 'a { input.split(',').map(str::parse) } fn trace_moves<'a>(input: &'a str) -> impl Iterator> + 'a { let mut pos = Position::default(); parse_moves(input).map(move |m| m.map(|m| pos.trace(m))) } fn closest_intersection(lines: &[Vec; 2]) -> u32 { lines[0][1..].iter().map(|a| lines[1][1..].iter().filter_map(move |b| a.collision(b).map(|p| p.manhatten_distance()))).flatten().min().unwrap() } fn shortest_delay(lines: &[Vec; 2]) -> i32 { lines[0][1..].iter().map(|a| lines[1][1..].iter().filter_map(move |b| a.collision(b).map(|p| p.steps))).flatten().min().unwrap() } fn parse(input: &str) -> [Vec; 2] { let mut lines = input.split_whitespace().map(trace_moves).map(Iterator::collect::, _>>); let result = [ lines.next().expect("require two lines").unwrap(), lines.next().expect("require two lines").unwrap(), ]; assert!(lines.next().is_none(), "require only two lines"); result } fn main() { let input: &str = include_str!("input.txt"); let lines = parse(input); println!("Distance 1: {}", closest_intersection(&lines)); println!("Distance 2: {}", shortest_delay(&lines)); } #[cfg(test)] mod day3test { use super::*; #[test] fn examples1() { assert_eq!(closest_intersection(&parse("R75,D30,R83,U83,L12,D49,R71,U7,L72 U62,R66,U55,R34,D71,R55,D58,R83")), 159); assert_eq!(closest_intersection(&parse("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51 U98,R91,D20,R16,D67,R40,U7,R15,U6,R7")), 135); } #[test] fn examples2() { assert_eq!(shortest_delay(&parse("R75,D30,R83,U83,L12,D49,R71,U7,L72 U62,R66,U55,R34,D71,R55,D58,R83")), 610); assert_eq!(shortest_delay(&parse("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51 U98,R91,D20,R16,D67,R40,U7,R15,U6,R7")), 410); } }