Skip to content

Commit 08ce80c

Browse files
sozelfistvil02
andauthored
Implement Rat in Maze Path Finder (#730)
* feat: implement Rat in Maze * ref: rewrite tests using macro * chore(docs): add `rat_in_maze` to `DIRECTORY.md` * fix: fix clippy issues * chore(ref): update implementation - Remove bad comments - Add some tests * ref: handle improper maze * ref: change `maze` representation from `Vec<Vec<usize>>` to `Vec<Vec<bool>>` * ref: refactor maze cell validation for clarity and efficiency * ref: refactor rat in maze implementation - Add custom errors to handle various exception cases: empty maze, non-rectangle maze, start out of maze bound - Add edge tests to handle various types of maze * ref: update implementation - Add `maze_with_going_back` test - Add explaination of why return this solution and not the other one when multiple path exist in maze --------- Co-authored-by: Piotr Idzik <[email protected]>
1 parent 1444e63 commit 08ce80c

File tree

3 files changed

+330
-0
lines changed

3 files changed

+330
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* [N Queens](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/n_queens.rs)
88
* [Parentheses Generator](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/parentheses_generator.rs)
99
* [Permutations](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/permutations.rs)
10+
* [Rat in Maze](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/rat_in_maze.rs)
1011
* [Sudoku](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/sudoku.rs)
1112
* Big Integer
1213
* [Fast Factorial](https://github.com/TheAlgorithms/Rust/blob/master/src/big_integer/fast_factorial.rs)

src/backtracking/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ mod knight_tour;
33
mod n_queens;
44
mod parentheses_generator;
55
mod permutations;
6+
mod rat_in_maze;
67
mod sudoku;
78

89
pub use all_combination_of_size_k::generate_all_combinations;
910
pub use knight_tour::find_knight_tour;
1011
pub use n_queens::n_queens_solver;
1112
pub use parentheses_generator::generate_parentheses;
1213
pub use permutations::permute;
14+
pub use rat_in_maze::find_path_in_maze;
1315
pub use sudoku::sudoku_solver;

src/backtracking/rat_in_maze.rs

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
//! This module contains the implementation of the Rat in Maze problem.
2+
//!
3+
//! The Rat in Maze problem is a classic algorithmic problem where the
4+
//! objective is to find a path from the starting position to the exit
5+
//! position in a maze.
6+
7+
/// Enum representing various errors that can occur while working with mazes.
8+
#[derive(Debug, PartialEq, Eq)]
9+
pub enum MazeError {
10+
/// Indicates that the maze is empty (zero rows).
11+
EmptyMaze,
12+
/// Indicates that the starting position is out of bounds.
13+
OutOfBoundPos,
14+
/// Indicates an improper representation of the maze (e.g., non-rectangular maze).
15+
ImproperMazeRepr,
16+
}
17+
18+
/// Finds a path through the maze starting from the specified position.
19+
///
20+
/// # Arguments
21+
///
22+
/// * `maze` - The maze represented as a vector of vectors where each
23+
/// inner vector represents a row in the maze grid.
24+
/// * `start_x` - The x-coordinate of the starting position.
25+
/// * `start_y` - The y-coordinate of the starting position.
26+
///
27+
/// # Returns
28+
///
29+
/// A `Result` where:
30+
/// - `Ok(Some(solution))` if a path is found and contains the solution matrix.
31+
/// - `Ok(None)` if no path is found.
32+
/// - `Err(MazeError)` for various error conditions such as out-of-bound start position or improper maze representation.
33+
///
34+
/// # Solution Selection
35+
///
36+
/// The function returns the first successful path it discovers based on the predefined order of moves.
37+
/// The order of moves is defined in the `MOVES` constant of the `Maze` struct.
38+
///
39+
/// The backtracking algorithm explores each direction in this order. If multiple solutions exist,
40+
/// the algorithm returns the first path it finds according to this sequence. It recursively explores
41+
/// each direction, marks valid moves, and backtracks if necessary, ensuring that the solution is found
42+
/// efficiently and consistently.
43+
pub fn find_path_in_maze(
44+
maze: &[Vec<bool>],
45+
start_x: usize,
46+
start_y: usize,
47+
) -> Result<Option<Vec<Vec<bool>>>, MazeError> {
48+
if maze.is_empty() {
49+
return Err(MazeError::EmptyMaze);
50+
}
51+
52+
// Validate start position
53+
if start_x >= maze.len() || start_y >= maze[0].len() {
54+
return Err(MazeError::OutOfBoundPos);
55+
}
56+
57+
// Validate maze representation (if necessary)
58+
if maze.iter().any(|row| row.len() != maze[0].len()) {
59+
return Err(MazeError::ImproperMazeRepr);
60+
}
61+
62+
// If validations pass, proceed with finding the path
63+
let mut maze_instance = Maze::new(maze.to_owned());
64+
Ok(maze_instance.find_path(start_x, start_y))
65+
}
66+
67+
/// Represents a maze.
68+
struct Maze {
69+
maze: Vec<Vec<bool>>,
70+
}
71+
72+
impl Maze {
73+
/// Represents possible moves in the maze.
74+
const MOVES: [(isize, isize); 4] = [(0, 1), (1, 0), (0, -1), (-1, 0)];
75+
76+
/// Constructs a new Maze instance.
77+
/// # Arguments
78+
///
79+
/// * `maze` - The maze represented as a vector of vectors where each
80+
/// inner vector represents a row in the maze grid.
81+
///
82+
/// # Returns
83+
///
84+
/// A new Maze instance.
85+
fn new(maze: Vec<Vec<bool>>) -> Self {
86+
Maze { maze }
87+
}
88+
89+
/// Returns the width of the maze.
90+
///
91+
/// # Returns
92+
///
93+
/// The width of the maze.
94+
fn width(&self) -> usize {
95+
self.maze[0].len()
96+
}
97+
98+
/// Returns the height of the maze.
99+
///
100+
/// # Returns
101+
///
102+
/// The height of the maze.
103+
fn height(&self) -> usize {
104+
self.maze.len()
105+
}
106+
107+
/// Finds a path through the maze starting from the specified position.
108+
///
109+
/// # Arguments
110+
///
111+
/// * `start_x` - The x-coordinate of the starting position.
112+
/// * `start_y` - The y-coordinate of the starting position.
113+
///
114+
/// # Returns
115+
///
116+
/// A solution matrix if a path is found or None if not found.
117+
fn find_path(&mut self, start_x: usize, start_y: usize) -> Option<Vec<Vec<bool>>> {
118+
let mut solution = vec![vec![false; self.width()]; self.height()];
119+
if self.solve(start_x as isize, start_y as isize, &mut solution) {
120+
Some(solution)
121+
} else {
122+
None
123+
}
124+
}
125+
126+
/// Recursively solves the Rat in Maze problem using backtracking.
127+
///
128+
/// # Arguments
129+
///
130+
/// * `x` - The current x-coordinate.
131+
/// * `y` - The current y-coordinate.
132+
/// * `solution` - The current solution matrix.
133+
///
134+
/// # Returns
135+
///
136+
/// A boolean indicating whether a solution was found.
137+
fn solve(&self, x: isize, y: isize, solution: &mut [Vec<bool>]) -> bool {
138+
if x == (self.height() as isize - 1) && y == (self.width() as isize - 1) {
139+
solution[x as usize][y as usize] = true;
140+
return true;
141+
}
142+
143+
if self.is_valid(x, y, solution) {
144+
solution[x as usize][y as usize] = true;
145+
146+
for &(dx, dy) in &Self::MOVES {
147+
if self.solve(x + dx, y + dy, solution) {
148+
return true;
149+
}
150+
}
151+
152+
// If none of the directions lead to the solution, backtrack
153+
solution[x as usize][y as usize] = false;
154+
return false;
155+
}
156+
false
157+
}
158+
159+
/// Checks if a given position is valid in the maze.
160+
///
161+
/// # Arguments
162+
///
163+
/// * `x` - The x-coordinate of the position.
164+
/// * `y` - The y-coordinate of the position.
165+
/// * `solution` - The current solution matrix.
166+
///
167+
/// # Returns
168+
///
169+
/// A boolean indicating whether the position is valid.
170+
fn is_valid(&self, x: isize, y: isize, solution: &[Vec<bool>]) -> bool {
171+
x >= 0
172+
&& y >= 0
173+
&& x < self.height() as isize
174+
&& y < self.width() as isize
175+
&& self.maze[x as usize][y as usize]
176+
&& !solution[x as usize][y as usize]
177+
}
178+
}
179+
180+
#[cfg(test)]
181+
mod tests {
182+
use super::*;
183+
184+
macro_rules! test_find_path_in_maze {
185+
($($name:ident: $start_x:expr, $start_y:expr, $maze:expr, $expected:expr,)*) => {
186+
$(
187+
#[test]
188+
fn $name() {
189+
let solution = find_path_in_maze($maze, $start_x, $start_y);
190+
assert_eq!(solution, $expected);
191+
if let Ok(Some(expected_solution)) = &solution {
192+
assert_eq!(expected_solution[$start_x][$start_y], true);
193+
}
194+
}
195+
)*
196+
}
197+
}
198+
199+
test_find_path_in_maze! {
200+
maze_with_solution_5x5: 0, 0, &[
201+
vec![true, false, true, false, false],
202+
vec![true, true, false, true, false],
203+
vec![false, true, true, true, false],
204+
vec![false, false, false, true, true],
205+
vec![false, true, false, false, true],
206+
], Ok(Some(vec![
207+
vec![true, false, false, false, false],
208+
vec![true, true, false, false, false],
209+
vec![false, true, true, true, false],
210+
vec![false, false, false, true, true],
211+
vec![false, false, false, false, true],
212+
])),
213+
maze_with_solution_6x6: 0, 0, &[
214+
vec![true, false, true, false, true, false],
215+
vec![true, true, false, true, false, true],
216+
vec![false, true, true, true, true, false],
217+
vec![false, false, false, true, true, true],
218+
vec![false, true, false, false, true, false],
219+
vec![true, true, true, true, true, true],
220+
], Ok(Some(vec![
221+
vec![true, false, false, false, false, false],
222+
vec![true, true, false, false, false, false],
223+
vec![false, true, true, true, true, false],
224+
vec![false, false, false, false, true, false],
225+
vec![false, false, false, false, true, false],
226+
vec![false, false, false, false, true, true],
227+
])),
228+
maze_with_solution_8x8: 0, 0, &[
229+
vec![true, false, false, false, false, false, false, true],
230+
vec![true, true, false, true, true, true, false, false],
231+
vec![false, true, true, true, false, false, false, false],
232+
vec![false, false, false, true, false, true, true, false],
233+
vec![false, true, false, true, true, true, false, true],
234+
vec![true, false, true, false, false, true, true, true],
235+
vec![false, false, true, true, true, false, true, true],
236+
vec![true, true, true, false, true, true, true, true],
237+
], Ok(Some(vec![
238+
vec![true, false, false, false, false, false, false, false],
239+
vec![true, true, false, false, false, false, false, false],
240+
vec![false, true, true, true, false, false, false, false],
241+
vec![false, false, false, true, false, false, false, false],
242+
vec![false, false, false, true, true, true, false, false],
243+
vec![false, false, false, false, false, true, true, true],
244+
vec![false, false, false, false, false, false, false, true],
245+
vec![false, false, false, false, false, false, false, true],
246+
])),
247+
maze_without_solution_4x4: 0, 0, &[
248+
vec![true, false, false, false],
249+
vec![true, true, false, false],
250+
vec![false, false, true, false],
251+
vec![false, false, false, true],
252+
], Ok(None::<Vec<Vec<bool>>>),
253+
maze_with_solution_3x4: 0, 0, &[
254+
vec![true, false, true, true],
255+
vec![true, true, true, false],
256+
vec![false, true, true, true],
257+
], Ok(Some(vec![
258+
vec![true, false, false, false],
259+
vec![true, true, true, false],
260+
vec![false, false, true, true],
261+
])),
262+
maze_without_solution_3x4: 0, 0, &[
263+
vec![true, false, true, true],
264+
vec![true, false, true, false],
265+
vec![false, true, false, true],
266+
], Ok(None::<Vec<Vec<bool>>>),
267+
improper_maze_representation: 0, 0, &[
268+
vec![true],
269+
vec![true, true],
270+
vec![true, true, true],
271+
vec![true, true, true, true]
272+
], Err(MazeError::ImproperMazeRepr),
273+
out_of_bound_start: 0, 3, &[
274+
vec![true, false, true],
275+
vec![true, true],
276+
vec![false, true, true],
277+
], Err(MazeError::OutOfBoundPos),
278+
empty_maze: 0, 0, &[], Err(MazeError::EmptyMaze),
279+
maze_with_single_cell: 0, 0, &[
280+
vec![true],
281+
], Ok(Some(vec![
282+
vec![true]
283+
])),
284+
maze_with_one_row_and_multiple_columns: 0, 0, &[
285+
vec![true, false, true, true, false]
286+
], Ok(None::<Vec<Vec<bool>>>),
287+
maze_with_multiple_rows_and_one_column: 0, 0, &[
288+
vec![true],
289+
vec![true],
290+
vec![false],
291+
vec![true],
292+
], Ok(None::<Vec<Vec<bool>>>),
293+
maze_with_walls_surrounding_border: 0, 0, &[
294+
vec![false, false, false],
295+
vec![false, true, false],
296+
vec![false, false, false],
297+
], Ok(None::<Vec<Vec<bool>>>),
298+
maze_with_no_walls: 0, 0, &[
299+
vec![true, true, true],
300+
vec![true, true, true],
301+
vec![true, true, true],
302+
], Ok(Some(vec![
303+
vec![true, true, true],
304+
vec![false, false, true],
305+
vec![false, false, true],
306+
])),
307+
maze_with_going_back: 0, 0, &[
308+
vec![true, true, true, true, true, true],
309+
vec![false, false, false, true, false, true],
310+
vec![true, true, true, true, false, false],
311+
vec![true, false, false, false, false, false],
312+
vec![true, false, false, false, true, true],
313+
vec![true, false, true, true, true, false],
314+
vec![true, false, true , false, true, false],
315+
vec![true, true, true, false, true, true],
316+
], Ok(Some(vec![
317+
vec![true, true, true, true, false, false],
318+
vec![false, false, false, true, false, false],
319+
vec![true, true, true, true, false, false],
320+
vec![true, false, false, false, false, false],
321+
vec![true, false, false, false, false, false],
322+
vec![true, false, true, true, true, false],
323+
vec![true, false, true , false, true, false],
324+
vec![true, true, true, false, true, true],
325+
])),
326+
}
327+
}

0 commit comments

Comments
 (0)