Through a Maze Darkly

Subscribe to Tech With Tim!

Download All: Download Now

Problem

This is an intermediate level programming problem. It shouldn’t take you more than 20 minutes to complete. For your reference it took me (Tech With Tim) 5 minutes to complete.
maze

Testing

To test your own solution simply download the test files from above and place your code in the function “run()”. Here’s the testing script in case you’re curious. The “.in” and “.out” files need to be in the same directory as your script!

"""
maze.py
Maze Problem from CCC Waterloo

Author: Tim
Date Modified: Mar-28-2019
"""
from os import listdir
from os.path import isfile
import time
from queue import Queue


def read_in_out():
    """
    My automated tester! Just goes through all the .in and .out files 
    in the current directory and checks output/expected output
    """

    print("* Testing Maze Problem *")

    onlyFiles = [f for f in listdir() if f.split(".")[-1] != "py" and isfile(f)]
    count = 0
    cases = 0
    wrong = 0
    timeOut = 0
    
    results = []
    while count < len(onlyFiles):
        cases+= 1
        inp = open(onlyFiles[count], "r").readlines()
        out = open(onlyFiles[count+1], "r").readlines()
        _in = [x.strip() for x in inp]
        _out = [x.strip() for x in out]

        start = time.time()
        ans = run(_in)

        if ans == _out:
            print("Found in " + str(time.time() - start)[:5] + "ms", end=" | ")
            print("CORRECT")
            results.append("# ")
        elif ans == None:
            timeOut += 1
            print("TIMEOUT")
            results.append("T ")
        else:
            wrong += 1
            print("Found in " + str(time.time() - start)[:5] + "ms", end=" | ")
            print("INCORRECT")
            print("Excpected: ")
            print(_out)
            print("Your Answer: ")
            print(ans)
            results.append("_")

        count += 2

    # OUTPUT RESULTS
    print("***** Results *****")
    print("Problems: " + str(cases))
    print("Correct: " + str(cases-wrong-timeOut))
    print("Timed Out: " + str(timeOut))
    print("Incorrect: " + str(wrong))
    print("Score: " + str(cases-wrong-timeOut) + "/" + str(cases))
    print(*results)
    print("* DONE Testing Maze Problem *")


# YOUR CODE HERE, return the answer as a list of strings
def run():
    pass

read_in_out()

Solution

Watch the video for a full explanation of the solution.

"""
maze.py
Maze Problem from CCC Waterloo

Author: Tim
Date Modified: Mar-28-2019
"""
from os import listdir
from os.path import isfile
import time
from queue import Queue


def read_in_out():
    """
    My automated tester! Just goes through all the .in and .out files 
    in the current directory and checks output/expected output
    """

    print("* Testing Maze Problem *")

    onlyFiles = [f for f in listdir() if f.split(".")[-1] != "py" and isfile(f)]
    count = 0
    cases = 0
    wrong = 0
    timeOut = 0
    
    results = []
    while count < len(onlyFiles):
        cases+= 1
        inp = open(onlyFiles[count], "r").readlines()
        out = open(onlyFiles[count+1], "r").readlines()
        _in = [x.strip() for x in inp]
        _out = [x.strip() for x in out]

        start = time.time()
        ans = run(_in)

        if ans == _out:
            print("Found in " + str(time.time() - start)[:5] + "ms", end=" | ")
            print("CORRECT")
            results.append("# ")
        elif ans == None:
            timeOut += 1
            print("TIMEOUT")
            results.append("T ")
        else:
            wrong += 1
            print("Found in " + str(time.time() - start)[:5] + "ms", end=" | ")
            print("INCORRECT")
            print("Excpected: ")
            print(_out)
            print("Your Answer: ")
            print(ans)
            results.append("_")

        count += 2

    # OUTPUT RESULTS
    print("***** Results *****")
    print("Problems: " + str(cases))
    print("Correct: " + str(cases-wrong-timeOut))
    print("Timed Out: " + str(timeOut))
    print("Incorrect: " + str(wrong))
    print("Score: " + str(cases-wrong-timeOut) + "/" + str(cases))
    print(*results)
    print("* DONE Testing Maze Problem *")


class Node:
    """
    Node class,
    simply stores neighboring nodes of a node
    """
    def __init__(self, neighbors):
        """
        neighbors are stored as ints *NOT <code>Node</code>'s*
        """
        self.neighbors = neighbors

    def __str__(self):
        return str(self.neighbors)


def run(inp):
    # Basic principle here is I have a dict that stores all my Node objects
    # when I want a nodes neighbors I use the node number or id as a key to access it's neighbors.
    # For the BFS I simply store lists containing the current path, so the last number in the list
    # will be the current node (ie the one that neighbors need to be explored next),
    # when the last node is the same as the first node in the list we have
    # achieved the goal. 

    # key i is node i
    maze = {}  # dict to store nodes

    # Split the input into two parts
    info = inp[1:int(inp[0])+1]  
    start_rooms = inp[int(inp[0])+2:]  # these are all of the start rooms

    start = time.time()  # so we can timeout

    # Convert each line of input into a list full of ints
    for i in range(1, len(info)+1):
        formatLine = list(map(lambda x: int(x), info[i-1].strip().split(" ")[1:]))
        maze[i] = Node(formatLine)

    # Stores the answers
    out = []
    for room in start_rooms:  # have to loop for each room asked of us
        room = int(room)
        mx = 0  # current max value

        # Simple FIFO Queue
        q = Queue()
        for n in maze[room].neighbors:
            q.put([room,n])  # Start the queue with each of the starting room nodes

        while not q.empty():  # BFS!

            # just timeout after 0.5 seconds
            if time.time() - start > 0.5:
                return None

            current = q.get()  # dequeue

            if current[-1] == room:  # if we end up back in the same room
                if len(current) > mx:
                    mx = len(current)
            else:
                new = current[:]  # copy the current path
                if len(maze[current[-1]].neighbors) == 1: # check if the current node has only one neighbor
                    new.append(maze[current[-1]].neighbors[0])
                else:
                    # here we are going to determine which neighbor we go to next, we have to go clockwise because of
                    # the problem specifications

                    last_pos = maze[current[-1]].neighbors.index(current[-2]) # find what hallway we came through

                    if last_pos + 1 >= len(maze[current[-1]].neighbors):  # if the hallway we came through was the last in the
                        last_pos = -1                                     # neighbor list (so most clockwise?) just go back to the first hall
                                                                          # were just trying to figure out what hall to chose next

                    add = maze[current[-1]].neighbors[last_pos + 1]  # add one to the last position and add that as next node
                    new.append(add)

                if new[-1] == room:  # if the last element is the room we started in
                    if len(current)+1 > mx:
                        mx = len(current) + 1
                else:
                    q.put(new)  # add new path to check

        out.append(str(mx-1))

    return out


read_in_out()