1

I'm trying to implement breadth first search in python. I'm trying to find a path through a grid, starting from one square and finding a path towards the goal square. There are obstacles throughout the grid marked by the character 'o'. The "graph" refers to a two dimensional array of Nodes, a simple class:

# Node class
class Node:
    def __init__(self, val, pos):
        self.val = val
        self.pos = pos
        self.visited = False
    def __str__(self):
        return "%s" % self.val

I realize this isn't the cleanest implementation of BFS--I don't have much experience working with python, so sometimes I had to use repetitive code because I wasn't sure how python handles the pointers beneath some of the local variables. Anyway, this BFS loops infinitely and I can't figure out why. Any help would be appreciated!

The fringe is basically a queue, and before moving a level deeper, each node's adjacent squares are checked in the order of left, up, right, and down.

# Breadth First Search
def bfs(arena, start):
    # fringe implemented as a FIFO list (behaves like a queue)
    fringe = []
    fringe.append([start])
    start.visited = True
    while fringe:
        # get the first path from the fringe queue
        path = fringe.pop(0)
        print "pop!"
        # get the last node from the path
        node = path[-1]
        # goal check
        if node.val == 'g':
            print "PATH FOUND!!"
            return path
        # get all adjacent nodes, construct a new path and push it to the fringe queue
        pos = node.pos
        # get left node first 
        if pos[1]-1>=0:
            neighbor = graph[pos[0]][pos[1]-1]
            newPath = path[:]
            if neighbor.val == 'o':
                neighbor.visited = True
                graph[pos[0]][pos[1]-1].visited = True
            if neighbor is not neighbor.visited: 
                neighbor.visited = True 
                graph[pos[0]][pos[1]-1].visited = True
                newPath.append(neighbor)
                fringe.append(newPath)
                print "left node added!"
        # get node above current node
        if pos[0]-1>=0:
            neighbor = graph[pos[0]-1][pos[1]]
            newPath = path[:]
            if neighbor.val == 'o':
                neighbor.visited = True
                graph[pos[0-1]][pos[1]].visited = True
            if neighbor is not neighbor.visited:
                neighbor.visited = True
                graph[pos[0-1]][pos[1]].visited = True
                newPath.append(neighbor)
                fringe.append(newPath)
                print "top noded added!"
        # get node to the right of current node
        if pos[1]+1 < columns:
            neighbor = graph[pos[0]][pos[1]+1]
            newPath = path[:]
            if neighbor.val == 'o':
                neighbor.visited = True
                graph[pos[0]][pos[1]+1].visited = True
            if neighbor is not neighbor.visited:
                neighbor.visited = True
                graph[pos[0]][pos[1]+1].visited = True
                newPath.append(neighbor)
                fringe.append(newPath)
                print "right node added!"
        # get node below current node
        if pos[0]+1 < rows:
            neighbor = graph[pos[0]+1][pos[1]]
            newPath = path[:]
            if neighbor.val == 'o':
                neighbor.visited = True
                graph[pos[0]+1][pos[1]].visited = True
            if neighbor is not neighbor.visited:
                neighbor.visited = True
                graph[pos[0]+1][pos[1]].visited = True
                newPath.append(neighbor)
                fringe.append(newPath)
                print "node below added!"
Emma
  • 35
  • 2
  • 11
  • 1
    you might want to look at this https://docs.python.org/2/tutorial/classes.html#class-and-instance-variables , Your Node class has some class variables, put them inside __init__, this might be a cause of your problem – Peeyush Sep 27 '14 at 03:29
  • thank you for pointing that out! i didn't realize that was how classes in python worked. I have updated that but the problem persists. I'll keep looking! – Emma Sep 27 '14 at 03:45
  • `fringe.append([start])` should be `fringe.append(start)`. – Pradhan Sep 27 '14 at 03:49
  • That's how I started, but shouldn't the fringe technically be a list of lists? (a list of pending paths?) If I make that change, it throws an error in the first iteration of the while loop at the line `node = path[-1]` – Emma Sep 27 '14 at 03:54
  • Emma, I see two major problems in your question: 1. Your code is half-baked, it even has undefined variables. You should try harder and, if asking anyone for help, at least add ready-to-run example with no syntax errors. 2. You lack basic Python knowledge. Find yourself a good book, it will make things much easier. – alexanderlukanin13 Sep 27 '14 at 06:59

1 Answers1

3

Here is working code, please carefully read the comments.

from pprint import pprint

# pos is (row, column), not (x, y)
class Node:
    def __init__(self, val, pos):
        self.val = val
        # Position info is stored here and ALSO as index in graph -
        # this is a form of data duplication, which is evil!
        self.pos = pos
        self.visited = False
    def __repr__(self):
        # nice repr for pprint
        return repr(self.pos)

# You had mistake here, "arena" instead of "graph"
# Before posting questions on stackoverflow, make sure your examples
# at least produce some incorrect result, not just crash.
# Don't make people fix syntax errors for you!
def bfs(graph, start):
    fringe = [[start]]
    # Special case: start == goal
    if start.val == 'g':
        return [start]
    start.visited = True
    # Calculate width and height dynamically. We assume that "graph" is dense.
    width = len(graph[0])
    height = len(graph)
    # List of possible moves: up, down, left, right.
    # You can even add chess horse move here!
    moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    while fringe:
        # Print fringe at each step
        pprint(fringe)
        print('')
        # Get first path from fringe and extend it by possible moves.
        path = fringe.pop(0)
        node = path[-1]
        pos = node.pos
        # Using moves list (without all those if's with +1, -1 etc.) has huge benefit:
        # moving logic is not duplicated. It will save you from many silly errors.
        # The example of such silly error in your code:
        # graph[pos[0-1]][pos[1]].visited = True
        #           ^^^
        # Also, having one piece of code instead of four copypasted pieces
        # will make algorithm much easier to change (e.g. if you want to move diagonally).
        for move in moves:
            # Check out of bounds. Note that it's the ONLY place where we check it. Simple and reliable!
            if not (0 <= pos[0] + move[0] < height and 0 <= pos[1] + move[1] < width):
                continue
            neighbor = graph[pos[0] + move[0]][pos[1] + move[1]]
            if neighbor.val == 'g':
                return path + [neighbor]
            elif neighbor.val == 'o' and not neighbor.visited:
                # In your original code there was a line:
                # if neighbor is not neighbor.visited:
                # which was completely wrong. Read about "is" operator.
                neighbor.visited = True
                fringe.append(path + [neighbor])  # creates copy of list
    raise Exception('Path not found!')

if __name__ == '__main__':
    # Graph in convenient form: 0 is empty, 1 is wall, 2 is goal.
    # Note that you can have multiple goals.
    graph = [
        [0, 1, 0, 1],
        [0, 0, 0, 0],
        [0, 1, 0, 1],
        [0, 0, 2, 1]
    ]
    # Transform int matrix to Node matrix.
    TRANSLATE = {0: 'o', 1: 'x', 2: 'g'}
    graph = [[Node(TRANSLATE[x], (i, j)) for j, x in enumerate(row)] for i, row in enumerate(graph)]
    # Find path
    try:
        path = bfs(graph, graph[0][0])
        print("Path found: {!r}".format(path))
    except Exception as ex:
        # Learn to use exceptions. In your original code, "no path" situation
        # is not handled at all!
        print(ex)
alexanderlukanin13
  • 4,577
  • 26
  • 29