common.rs (6774B)
1 // This code is more complicated than it should be. The reason is that 2 // for part 1 I decided to ignore the start and finish position, and not 3 // to treat them as other cells. This way I could get away with not 4 // considering wall cells '#' at all. 5 // But then for part 2 it was more convenient to include them, so I 6 // added an enum Position {} that treats start and finish as special 7 // cases. This is quite messy. 8 9 use std::collections::{VecDeque, HashSet}; 10 11 #[derive(PartialEq, Debug)] 12 pub enum Direction { Up, Down, Left, Right, None } 13 14 impl Direction { 15 pub fn from_byte(b: u8) -> Direction { 16 match b { 17 b'^' => Direction::Up, 18 b'v' => Direction::Down, 19 b'>' => Direction::Right, 20 b'<' => Direction::Left, 21 b'.' => Direction::None, 22 x => panic!("unexpected char '{}'", x as char) 23 } 24 } 25 } 26 27 #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] 28 pub enum Position { 29 Start, 30 Finish, 31 Other { i: usize, j: usize } 32 } 33 34 impl Position { 35 pub fn dist(&self, other: Position) -> usize { 36 match (*self, other) { 37 (Position::Start, Position::Start) | 38 (Position::Finish, Position::Finish) => 39 0, 40 (Position::Start, Position::Finish) | 41 (Position::Finish, Position::Start) => 42 usize::MAX, // Not technically correct, but it works 43 (Position::Start | Position::Finish, Position::Other { i, j }) | 44 (Position::Other { i, j }, Position::Start | Position::Finish) => 45 i + j, 46 (Position::Other { i: i1, j: j1 }, 47 Position::Other { i: i2, j: j2 }) => { 48 let i = (i1 as i64 - i2 as i64).abs() as usize; 49 let j = (j1 as i64 - j2 as i64).abs() as usize; 50 i + j 51 } 52 } 53 } 54 } 55 56 pub struct Map { 57 pub tiles: Vec<Vec<Direction>>, 58 pub imax: usize, 59 pub jmax: usize 60 } 61 62 #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] 63 struct Node { 64 p: Position, 65 t: usize 66 } 67 68 impl Map { 69 fn submod(i: usize, t: usize, m: usize) -> usize { 70 let maybeneg = ((i as i64) - (t as i64)) % m as i64; 71 (maybeneg as usize + m) % m 72 } 73 74 pub fn is_busy(&self, p: Position, t: usize) -> bool { 75 match p { 76 Position::Start | Position::Finish => false, 77 Position::Other { i, j } => { 78 let iup = Self::submod(i, t, self.imax); 79 let idown = (i + t) % self.imax; 80 let jleft = Map::submod(j, t, self.jmax); 81 let jright = (j + t) % self.jmax; 82 83 self.tiles[iup][j] == Direction::Down || 84 self.tiles[idown][j] == Direction::Up || 85 self.tiles[i][jleft] == Direction::Right || 86 self.tiles[i][jright] == Direction::Left 87 } 88 } 89 } 90 91 #[allow(dead_code)] 92 pub fn print(&self, t: usize) { 93 print!("#."); 94 for _ in 0..self.jmax { print!("#"); } 95 println!(); 96 97 for i in 0..self.imax { 98 print!("#"); 99 for j in 0..self.jmax { 100 let p = Position::Other { i, j }; 101 print!("{}", if self.is_busy(p, t) { '*' } else { '.' }); 102 } 103 println!("#"); 104 } 105 106 for _ in 0..self.jmax { print!("#"); } 107 print!(".#"); 108 println!(); 109 } 110 111 fn mv(&self, p: Position, d: Direction) -> Option<Position> { 112 if d == Direction::None { return Some(p); } 113 match p { 114 Position::Start => if d == Direction::Down { 115 Some(Position::Other { i: 0, j: 0 }) 116 } else { None }, 117 Position::Finish => if d == Direction::Up { 118 Some(Position::Other { i: self.imax-1, j: self.jmax-1 }) 119 } else { None }, 120 Position::Other { i, j } => match d { 121 Direction::Up => if i > 0 { 122 Some(Position::Other { i: i-1, j }) 123 } else { 124 if i == 0 && j == 0 { 125 Some(Position::Start) 126 } else { None } 127 }, 128 Direction::Down => if i < self.imax-1 { 129 Some(Position::Other { i: i+1, j }) 130 } else { 131 if i == self.imax-1 && j == self.jmax-1 { 132 Some(Position::Finish) 133 } else { None } 134 }, 135 Direction::Left => if j > 0 { 136 Some(Position::Other { i, j: j-1 }) 137 } else { None }, 138 Direction::Right => if j < self.jmax-1 { 139 Some(Position::Other { i, j: j+1 }) 140 } else { None }, 141 Direction::None => panic!("stupid rust") 142 } 143 } 144 } 145 146 fn maybe_push(&self, x: Node, d: Direction, v: &mut HashSet<Node>) { 147 if let Some(p) = self.mv(x.p, d) { 148 if !self.is_busy(p, x.t+1) { 149 v.insert(Node { p, t: x.t+1 }); 150 } 151 } 152 } 153 154 pub fn shortest_path( 155 &self, 156 start: Position, 157 finish: Position, 158 t0: usize 159 ) -> usize { 160 let mut vnext = HashSet::<Node>::new(); 161 vnext.insert(Node { p: start, t: t0 }); 162 while !vnext.is_empty() { 163 let mut v = vnext.clone().into_iter().collect::<Vec<_>>(); 164 // Heuristic: explore first the positions closer to the target 165 v.sort_by(|a, b| a.p.dist(finish).cmp(&(b.p.dist(finish)))); 166 let mut v = VecDeque::<Node>::from(v); 167 vnext.clear(); 168 while !v.is_empty() { 169 let x = v.pop_front().unwrap(); 170 if x.p == finish { return x.t; } 171 172 self.maybe_push(x, Direction::Up, &mut vnext); 173 self.maybe_push(x, Direction::Down, &mut vnext); 174 self.maybe_push(x, Direction::Left, &mut vnext); 175 self.maybe_push(x, Direction::Right, &mut vnext); 176 if !self.is_busy(x.p, x.t+1) { 177 self.maybe_push(x, Direction::None, &mut vnext); 178 } 179 } 180 } 181 panic!("queue is empty"); 182 } 183 } 184 185 pub fn read_map_from_stdin() -> Map { 186 let mut tiles = vec![]; 187 let mut line = String::new(); 188 while std::io::stdin().read_line(&mut line).unwrap() > 0 { 189 let bytes = line.as_bytes(); 190 if bytes[2] == b'#' { 191 line.clear(); 192 continue; 193 } 194 let i = tiles.len(); 195 tiles.push(vec![]); 196 for j in 1..bytes.len()-2 { 197 tiles[i].push(Direction::from_byte(bytes[j])); 198 } 199 line.clear(); 200 } 201 let imax = tiles.len(); 202 let jmax = tiles[0].len(); 203 Map { tiles, imax, jmax } 204 }