191 lines
5.0 KiB
Rust
191 lines
5.0 KiB
Rust
|
type Error = Box<dyn std::error::Error>;
|
||
|
|
||
|
#[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<Position> {
|
||
|
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<Self, Self::Err> {
|
||
|
let e = move || Box::new(std::io::Error::new(std::io::ErrorKind::Other, "invalid move"));
|
||
|
let n = s[1..].parse::<u32>()?;
|
||
|
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<Item=Result<Move, Error>> + 'a {
|
||
|
input.split(',').map(str::parse)
|
||
|
}
|
||
|
|
||
|
fn trace_moves<'a>(input: &'a str) -> impl Iterator<Item=Result<Segment, Error>> + 'a {
|
||
|
let mut pos = Position::default();
|
||
|
parse_moves(input).map(move |m| m.map(|m| pos.trace(m)))
|
||
|
}
|
||
|
|
||
|
fn closest_intersection(lines: &[Vec<Segment>; 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<Segment>; 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<Segment>; 2] {
|
||
|
let mut lines = input.split_whitespace().map(trace_moves).map(Iterator::collect::<Result<Vec<_>, _>>);
|
||
|
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);
|
||
|
}
|
||
|
}
|