This is the solution to the Damavis Data Engineer Challenge 2023 by Álvaro Videgain Barranco. It includes a Python code implementation of a labyrinth solver. The code takes a nested list representation of a labyrinth and finds the minimum number of steps required to move a rod from the top left corner to the bottom right corner of the labyrinth, considering certain movement and rotation rules which are specified in the challenge instructions.
No external dependencies are needed. The code was developed and tested in Python 3.10.7.
The labyrinth is internally represented using three main data classes:
- Represents the position of a cell in the labyrinth grid.
- Contains
x
andy
coordinates, starting from zero. x
increases when moving to the right, andy
increases when moving down.
- Represents the position and orientation of the rod in the labyrinth.
- Contains a
pos
field of typeCoordinates
representing the current position of its center. - Contains an
orientation
field of typeOrientation
representing the current orientation of the rod. - The
Orientation
enumeration defines two possible values:HORIZONTAL
andVERTICAL
.
- Represents the entire system state.
- Contains the size of the labyrinth (
n_rows
andn_cols
). - Contains a
walls
field, which is a tuple ofCoordinates
representing the positions of walls in the labyrinth. - Contains a
rod
field of typeRodState
, representing the state of the rod. - Contains a
steps
field representing the number of steps taken to reach the current state.
A custom exception is raised when the labyrinth is not valid: InvalidLabyrinthError
.
The labyrinth_parser()
function takes the raw labyrinth representation and converts it into a LabyrinthState
object. It validates the input labyrinth using validate_lab()
that performs various checks to validate the labyrinth's size, shape, characters, and initial state.
The solve_labyrinth()
function implements a breadth-first search algorithm to find the minimum number of steps required to reach the bottom right corner of the labyrinth. It starts from the initial state and follows this steps, finding new states to explore:
-
Check if the current state has already been explored, if this is the case, it jumps to the next iteration. In order to track the explored states the list
visited
is used. -
Find all the potential positions to be reached from the current states and adds them to the states to explore. For this purpose, the function
get_valid_moves()
is used, which takes into account the movement and rotation rules. -
Check if the bottom right corner has been reached, in this case return the number of steps required to reach it. The number of steps are stored in the field
steps
ofLabyrinthState
. -
if the bottom right corner has not been reached, explore the next state in a new iteration. As a breadth-first search algorithm is being used, the states to be explored are accessed following a first in, first out (FIFO) method, which is implemented using a Python
collections.deque
. -
When there are no states left to be explored, return -1 as the labyrinth can not be solved.
The file LabyrinthSearch.py
contains the solution logic. To use the labyrinth solver, call min_dist_lab()
and pass in the nested list representation of the labyrinth. It will return the minimum number of steps required to solve the labyrinth, -1 if it is not possible to solve it or raise a InvalidLabyrinthError
if the labyrinth input is invalid.
The file TestLabyrinth.py
contains a set of unit tests to verify the correctness of the labyrinth solver. The test cases cover different scenarios, including solvable labyrinths, unsolvable labyrinths, and invalid inputs. In order to not use external frameworks, the standard library unittest
module is used, instead of other options, such as pytest
.