Prerequisites: Python programming, Object-oriented programming, Linkedlist
888b d888 d8888 8888888888P 8888888888 8888888b. 888 888 888b 888 888b 888 8888888888 8888888b.
8888b d8888 d88888 d88P 888 888 Y88b 888 888 8888b 888 8888b 888 888 888 Y88b
88888b.d88888 d88P888 d88P 888 888 888 888 888 88888b 888 88888b 888 888 888 888
888Y88888P888 d88P 888 d88P 8888888 888 d88P 888 888 888Y88b 888 888Y88b 888 8888888 888 d88P
888 Y888P 888 d88P 888 d88P 888 8888888P" 888 888 888 Y88b888 888 Y88b888 888 8888888P"
888 Y8P 888 d88P 888 d88P 888 888 T88b 888 888 888 Y88888 888 Y88888 888 888 T88b
888 " 888 d8888888888 d88P 888 888 T88b Y88b. .d88P 888 Y8888 888 Y8888 888 888 T88b
888 888 d88P 888 d8888888888 8888888888 888 T88b "Y88888P" 888 Y888 888 Y888 8888888888 888 T88b
This project contains an algorithm that figures out a way out of the maze.
The implementation here uses a Stack to store the coordinates to be explored at each step. The Stack data structure is implemented using a LinkedList.
main.py and maze.py contain complete code to solve a maze, but datastruct.py and adt.py are incomplete!
- Implement the
LinkedListclass indatastruct.pyto enable the maze to be solved. - Use your own tests, to check your
LinkedListimplementation.
Remember to remove the raise NotImplementedError lines. While the pass keyword allows the program to continue silently without any code to run, the NotImplementedError halts the program. Very useful to remind yourself of parts of the code that are not yet ready and still need work!
Technical notes:
LinkedList is a data structure. That means there is a detailed specification for how it should be implemented.
The program in main.py uses a Stack to implement a depth-first search strategy. This approach checks a single route at a time, exploring until it reaches a dead end before backtracking to the last unexplored fork.
- Implement the
StackandQueueclasses inadt.pyto enable the maze to be solved. - Use your own tests, to check your
StackandQueueimplementations.
If it works correctly, you should see the program successfully find its way out of the maze! - Observe the difference in the way the maze is explored when Stack and Queue are used.
- Edit the program in
main.py, replacing the Stack with a Queue (and using appropriate queue methods). This switches the strategy to a breadth-first search. All encountered routes are checked simultaneously and advance one step each cycle.
Technical notes:
Stacks and queues are not data structures; instead, they are abstract data types. That means:
- "Abstract": There is no detailed specification for they should be implemented. One can reasonably use any implementation so long as the behaviour of the class is correct.
However, the choice of implementation will have implications for efficiency. - "Data type": there are expectations of how stacks and queues should behave.
- Stacks are expected to behave first-in-last-out (FILO)
- Queues are expected to behave first-in-first-out (FIFO)
A Circular Queue is a fixed size queue where the next item that comes after the tail (last item) is the head (first item).
The fixed size of a Circular Queue makes it much easier to implement enqueue() and dequeue() methods, as there is no need to implement insert/append/delete methods. Instead, the head and tail attributes keep track of the indexes where exising items will be dequeued, and where new items will be enqueued.
- Implement the
CircularQueueclass incq.pyto enable the maze to be solved. - Use your own tests, to check your
Circular Queueimplementation. - Edit
main.pyto use aCircularQueueclass instead of aQueue.
If it works correctly, you should see the program successfully find its way out of the maze (using breadth-first search)!
There is no standard convention which end the circular queue should be dequeued at or dequeued from. For this project, we will enqueue at tail and dequeue from head.
When the circular queue is empty, no dequeueing is possible. The head is thus initialised with an invalid index, and reset to the same invalid index whenever the queue is empty again.
When the circular queue is full, no enqueueing is possible (because the size is fixed). The tail is set to an invalid index whenever the queue is full.
When implemented as a (fixed-size) Array, additional logic has to be written for the head/tail indexes to "wrap around" to 0 after the last index. This can be abstracted using a private increment() method.
When enqueueing an item, the following operations need to be carried out:
- Check if queue is full (
tailis invalid), raise an error if so. - Add the item at the
tailindex. - Increment the
tailindex, with wrap-around. - Check if the queue is full (
tailpoints tohead), invalidateheadif necessary.
When dequeueing an item, the following operations need to be carried out:
- Check if queue is empty (
headis invalid), raise an error if so. - Increment the
headindex, with wrap-around. - Check if the queue is empty (
headpoints totail), invalidatetailif necessary. - Return the item
Technical notes:
CircularQueue is both a data structure and an abstract data type. Circular queues are expected to be statically allocated, which typically implies using static arrays.
At the same time, there are expectations of how the circular queue should behave. The circular queue must follow the same behaviour as queues, and it must be implemented such that the head wraps around to its tail.