diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index 7bde886dd558..626c83cb4a15 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -1,91 +1,164 @@ from __future__ import annotations -def solve_maze(maze: list[list[int]]) -> bool: +def solve_maze( + maze: list[list[int]], + source_row: int, + source_column: int, + destination_row: int, + destination_column: int, +) -> list[list[int]]: """ This method solves the "rat in maze" problem. - In this problem we have some n by n matrix, a start point and an end point. - We want to go from the start to the end. In this matrix zeroes represent walls - and ones paths we can use. Parameters : - maze(2D matrix) : maze + - maze: A two dimensional matrix of zeros and ones. + - source_row: The row index of the starting point. + - source_column: The column index of the starting point. + - destination_row: The row index of the destination point. + - destination_column: The column index of the destination point. Returns: - Return: True if the maze has a solution or False if it does not. + - solution: A 2D matrix representing the solution path if it exists. + Raises: + - ValueError: If no solution exists or if the source or + destination coordinates are invalid. + Description: + This method navigates through a maze represented as an n by n matrix, + starting from a specified source cell and + aiming to reach a destination cell. + The maze consists of walls (1s) and open paths (0s). + By providing custom row and column values, the source and destination + cells can be adjusted. >>> maze = [[0, 1, 0, 1, 1], ... [0, 0, 0, 0, 0], ... [1, 0, 1, 0, 1], ... [0, 0, 1, 0, 0], ... [1, 0, 0, 1, 0]] - >>> solve_maze(maze) - [1, 0, 0, 0, 0] - [1, 1, 1, 1, 0] - [0, 0, 0, 1, 0] - [0, 0, 0, 1, 1] - [0, 0, 0, 0, 1] - True + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[0, 1, 1, 1, 1], + [0, 0, 0, 0, 1], + [1, 1, 1, 0, 1], + [1, 1, 1, 0, 0], + [1, 1, 1, 1, 0]] + + Note: + In the output maze, the zeros (0s) represent one of the possible + paths from the source to the destination. >>> maze = [[0, 1, 0, 1, 1], ... [0, 0, 0, 0, 0], ... [0, 0, 0, 0, 1], ... [0, 0, 0, 0, 0], ... [0, 0, 0, 0, 0]] - >>> solve_maze(maze) - [1, 0, 0, 0, 0] - [1, 0, 0, 0, 0] - [1, 0, 0, 0, 0] - [1, 0, 0, 0, 0] - [1, 1, 1, 1, 1] - True + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[0, 1, 1, 1, 1], + [0, 1, 1, 1, 1], + [0, 1, 1, 1, 1], + [0, 1, 1, 1, 1], + [0, 0, 0, 0, 0]] >>> maze = [[0, 0, 0], ... [0, 1, 0], ... [1, 0, 0]] - >>> solve_maze(maze) - [1, 1, 1] - [0, 0, 1] - [0, 0, 1] - True + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[0, 0, 0], + [1, 1, 0], + [1, 1, 0]] - >>> maze = [[0, 1, 0], + >>> maze = [[1, 0, 0], ... [0, 1, 0], ... [1, 0, 0]] - >>> solve_maze(maze) - No solution exists! - False + >>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE + [[1, 0, 0], + [1, 1, 0], + [1, 1, 0]] + + >>> maze = [[1, 1, 0, 0, 1, 0, 0, 1], + ... [1, 0, 1, 0, 0, 1, 1, 1], + ... [0, 1, 0, 1, 0, 0, 1, 0], + ... [1, 1, 1, 0, 0, 1, 0, 1], + ... [0, 1, 0, 0, 1, 0, 1, 1], + ... [0, 0, 0, 1, 1, 1, 0, 1], + ... [0, 1, 0, 1, 0, 1, 1, 1], + ... [1, 1, 0, 0, 0, 0, 0, 1]] + >>> solve_maze(maze,0,2,len(maze)-1,2) # doctest: +NORMALIZE_WHITESPACE + [[1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1]] + >>> maze = [[1, 0, 0], + ... [0, 1, 1], + ... [1, 0, 1]] + >>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1) + Traceback (most recent call last): + ... + ValueError: No solution exists! + + >>> maze = [[0, 0], + ... [1, 1]] + >>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) + Traceback (most recent call last): + ... + ValueError: No solution exists! >>> maze = [[0, 1], ... [1, 0]] - >>> solve_maze(maze) - No solution exists! - False + >>> solve_maze(maze,2,0,len(maze)-1,len(maze)-1) + Traceback (most recent call last): + ... + ValueError: Invalid source or destination coordinates + + >>> maze = [[1, 0, 0], + ... [0, 1, 0], + ... [1, 0, 0]] + >>> solve_maze(maze,0,1,len(maze),len(maze)-1) + Traceback (most recent call last): + ... + ValueError: Invalid source or destination coordinates """ size = len(maze) + # Check if source and destination coordinates are Invalid. + if not (0 <= source_row <= size - 1 and 0 <= source_column <= size - 1) or ( + not (0 <= destination_row <= size - 1 and 0 <= destination_column <= size - 1) + ): + raise ValueError("Invalid source or destination coordinates") # We need to create solution object to save path. - solutions = [[0 for _ in range(size)] for _ in range(size)] - solved = run_maze(maze, 0, 0, solutions) + solutions = [[1 for _ in range(size)] for _ in range(size)] + solved = run_maze( + maze, source_row, source_column, destination_row, destination_column, solutions + ) if solved: - print("\n".join(str(row) for row in solutions)) + return solutions else: - print("No solution exists!") - return solved + raise ValueError("No solution exists!") -def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) -> bool: +def run_maze( + maze: list[list[int]], + i: int, + j: int, + destination_row: int, + destination_column: int, + solutions: list[list[int]], +) -> bool: """ This method is recursive starting from (i, j) and going in one of four directions: up, down, left, right. If a path is found to destination it returns True otherwise it returns False. - Parameters: - maze(2D matrix) : maze + Parameters + maze: A two dimensional matrix of zeros and ones. i, j : coordinates of matrix - solutions(2D matrix) : solutions + solutions: A two dimensional matrix of solutions. Returns: Boolean if path is found True, Otherwise False. """ size = len(maze) # Final check point. - if i == j == (size - 1): - solutions[i][j] = 1 + if i == destination_row and j == destination_column and maze[i][j] == 0: + solutions[i][j] = 0 return True lower_flag = (not i < 0) and (not j < 0) # Check lower bounds @@ -93,21 +166,27 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) if lower_flag and upper_flag: # check for already visited and block points. - block_flag = (not solutions[i][j]) and (not maze[i][j]) + block_flag = (solutions[i][j]) and (not maze[i][j]) if block_flag: # check visited - solutions[i][j] = 1 + solutions[i][j] = 0 # check for directions if ( - run_maze(maze, i + 1, j, solutions) - or run_maze(maze, i, j + 1, solutions) - or run_maze(maze, i - 1, j, solutions) - or run_maze(maze, i, j - 1, solutions) + run_maze(maze, i + 1, j, destination_row, destination_column, solutions) + or run_maze( + maze, i, j + 1, destination_row, destination_column, solutions + ) + or run_maze( + maze, i - 1, j, destination_row, destination_column, solutions + ) + or run_maze( + maze, i, j - 1, destination_row, destination_column, solutions + ) ): return True - solutions[i][j] = 0 + solutions[i][j] = 1 return False return False @@ -115,4 +194,4 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) if __name__ == "__main__": import doctest - doctest.testmod() + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)