0

I have imported a text file with numbers as the following example:


3 0 0 0 0 1 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 2 3 3 3 0 3 0 0 0 0 3 3 3 3 0 3 0 0 0 0 3 3 3 3 0 3 2 2 0 0 3 3 3 3 3 3 3 3 2 0 3 3 3


The goal is to read in the text file, format it as a grid (i.e a 10 by 10 grid) which I am able to do, and then sort through the list of lists to reach the solution where the number 3 is an obstacle, number 1 is start point and number 2 is the solution, I am attempting to use a BFS algorithm where the agent can move UP, DOWN, LEFT, RIGHT.

I am trying to print the sequence of steps that is taken to reach the closest solution (i.e 2) from the beginning point(i.e 1). The numbers are formatted as strings/ text. The program I have written seems to be running but it never prints a solution or terminates. The move sequence that is to be printed as a solutions is in the format of:


'Move Down' 'Move UP' ETC. where each move is on a newline

I am attaching my code below and any help that can be offered would be greatly appreciated

    import queue


def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name


def read_to_grid():
    file_name = read_user_input()
    for nums in open(file_name):
        line = list(nums.split())
        result = []
        for _ in range(0, len(line), 10):
            result.append(line[_:_ + 10])
        return result
    file_name.close()


def print_grid(result, path=''):
    for x, pos in enumerate(result[0]):
        if pos == '0':
            start = x

    i = start
    j = 0
    pos = set()
    for move in path:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

        pos.add((j, i))
    for j, row in enumerate(result):
        for i, col in enumerate(row):
            if (j, i) in pos:
                print('#', end='')
            else:
                print(col + ' ', end='')
        print()


def valid(result, moves):
    for x, pos in enumerate(result[0]):
        if pos == '0':
            start = x

    i = start
    j = 0
    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1
        if not (0 <= i < len(result[0]) and 0 <= j < len(result)):
            return False
        elif (result[i][j] == '3'):
            return False

    return True


def find_goal(result, moves):
    for x, pos in enumerate(result[0]):
        if pos == '0':
            start = x

    i = start
    j = 0
    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

    if result[j][i] == '2':
        print('Found: ' + moves)
        print_grid(result, moves)
        return True

    return False


nums = queue.Queue()
nums.put('')
add = ''
result = read_to_grid()

while not find_goal(result, add):
    add = nums.get()
    for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
        put = add + j
        if valid(result, put):
            nums.put(put)
  • Your program hangs, because it tries to get a value from an empty queue without a timeout. In general, your program design is a bit wasteful in that it always retraces the whole path from the beginning. Also, `moves` is a string and iterating over that will iterate over the individual characters. Perhaps it is better to be a bit more terse and just use one of `"UDLR"` as directions. – M Oehm Apr 08 '21 at 05:59

2 Answers2

0

Whilst debugging your code I ran into some endless loops and other bugs when it came to your 'valid' and 'find_goal' functions.

In my experience with breadth first search its best to treat each point as a node (coordinates in this case) and to have your queue consist of lists of paths that are currently being tried. Where each path is a list of each node thats transversed. Typically you don't want to visit the same node more than once in a given path so you'll have to keep track of this information rather than soley 'left', 'right', etc...

All that said, I built-off your code and created a function that would return the valid adjacent nodes when given a node accounting for grid bound, not being a 3 and wether the node has been visited or not. Then for the BFS part the queue starts with a list containing the starting node (I made a function to find where the 1 was). Then while a queue exists the BFS will pop off the current path, get the last node in that path, find all valid adjacent nodes. With each valid adjacent node a new path entry will be added to the queue consisting of the old path + the adjacent node. If one of the adjacent nodes is a goal it will end the search and return the path. I've included the directional information in the path so that you can parse that out.

This should print off a path to the nearest 2 as such:

[((5, 0), ''), ((5, 1), 'Down'), ((6, 1), 'Right'), ((6, 2), 'Down'), ((7, 2), 'Right'), ((7, 3), 'Down'), ((7, 4), 'Down'), ((7, 5), 'Down')]

You'll see the ...sorted(path_queue, key=lambda... that line isn't needed but is a lazy way to prioritize the queue, always trying the shortest current path. If you remove it you'll see you still get a valid path but its much longer.

def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name


def read_to_grid():
    file_name = read_user_input()
    for nums in open(file_name):
        line = list(nums.split())
        result = []
        for _ in range(0, len(line), 10):
            result.append(line[_:_ + 10])

    int_result = []
    for i, row in enumerate(result):
        int_result.append([])
        for col in row:
            int_result[i].append(int(col))
    
    return int_result


def print_grid(result, path=''):
    for x, pos in enumerate(result[0]):
        if pos == 0:
            start = x

    i = start
    j = 0
    pos = set()
    for move in path:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

        pos.add((j, i))
    for j, row in enumerate(result):
        for i, col in enumerate(row):
            if (j, i) in pos:
                print('#', end='')
            else:
                print(str(col) + ' ', end='')
        print()


def find_start_node(grid):
    for i, row in enumerate(grid):
        if 1 in row:
            return ((row.index(1), i), '')

    return (None, None)

def valid_adj(cur_node, grid, visited):
    x = cur_node[0][0]
    y = cur_node[0][1]
    
    adj = []

    if ((y + 1) < 10) and (grid[y + 1][x] != 3) and  not (any((x, y + 1) in node for node in visited)):
        adj.append(((x, y + 1), 'Down'))

    if ((x + 1) < 10) and (grid[y][x + 1] != 3) and not (any((x + 1, y) in node for node in visited)):
        adj.append(((x + 1, y), 'Right'))

    if ((y - 1) >= 0) and (grid[y - 1][x] != 3) and not (any((x, y - 1) in node for node in visited)):
        adj.append(((x, y - 1), 'Up'))

    if ((x - 1) >= 0) and (grid[y][x - 1] != 3) and not (any((x - 1, y) in node for node in visited)):
        adj.append(((x - 1, y), "Left"))

    return adj

def BFS(grid):
    start_node = find_start_node(grid)
    path_queue = [[start_node]]

    while path_queue:
        path_queue = sorted(path_queue, key=lambda x: len(x), reverse=True) # More optimized to guarantee shortest path, not needed

        cur_path = path_queue.pop()
        cur_node = cur_path[-1]

        if cur_node not in cur_path[:].pop():
            adj = valid_adj(cur_node, grid, cur_path)

            for node in adj:
                new_path = list(cur_path)
                new_path.append(node)

                path_queue.append(new_path)

                if grid[node[0][1]][node[0][0]] == 2:
                    print('path found')
                    return new_path

    return -1

grid = read_to_grid()

print_grid(grid)
print(BFS(grid))
Ryan Rau
  • 153
  • 1
  • 7
0

Ok Ryan answer already says everything, here however is your code working though is not efficient in anyway, the only things I changed that is worth is that instead of using a list of list you can just use a list, and the valid function now check the traveled path so that it can know where it has been so it won't loop.

import queue

# Read name file from user
def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name

# Read file and return list of list[10]
def read_to_grid():
    with open(read_user_input()) as file:
        for nums in file:
            line = list(nums.split())
            return line

# Shows a text grid
def print_grid(result, path=[]):
    for x, pos in enumerate(result):
        if pos == '1':
            start = x

    i = start
    #j = 0
    pos = set()
    
    for move in path:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            i -= 10
        elif move == 'Move Down':
            i += 10

        pos.add(i)

    for i, celd in enumerate(result):
        if i % 10 == 0:
            print()
        if i in pos:
            print('# ', end='')
        else:
            print(celd + ' ', end='')      

# Validates coordinates and traveled path
def valid(result, moves):
    for x, pos in enumerate(result):
        if pos == '1':
            start = x

    i = start % 10
    j = start // 10

    # Where we start
    travel = [(j,i)]

    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

        # Check if we have already been there
        if (j, i) in travel:
            return False
        else:
            travel += [(j,i)]

        # Check coordinates
        if i >= 10 or i < 0 or j >= len(result) // 10 or j < 0:
            return False
        elif result[i+j*10] == '3':
            return False

    return True

# Return true if 2 is reached
def find_goal(result, moves):
    for x, pos in enumerate(result):
        if pos == '1':
            start = x

    i = start
    #j = 0
    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            i -= 10
        elif move == 'Move Down':
            i += 10

    if result[i] == '2':
        print('Found: ',' '.join(moves))
        print_grid(result, moves[0:-1])
        return True

    return False

nums = queue.Queue()
result = read_to_grid()
add = []

while not find_goal(result, add):
    if not nums.empty():
        add = nums.get()
    for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
        put = add + [j]
        if valid(result, put):
            nums.put(put)

EDIT:

I cleaned up a little:

import queue

# Read name file from user
def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name

# Read file and return list of list[10]
def read_to_grid():
    with open(read_user_input()) as file:
        for nums in file:
            line = list(nums.split())
            return line

# Shows a text grid
def print_grid(result, path=[]):
    pos = set()
    
    for (x,y), _ in path:
        i = x + y*10
        pos.add(i)

    for i, celd in enumerate(result):
        if i % 10 == 0:
            print()
        if i in pos:
            print('# ', end='')
        else:
            print(celd + ' ', end='')      

# Validates coordinates and traveled path
def valid(result, moves):
    # Unpack
    (i,j), _ = moves[-1]

    # Check if already traveled
    if any(x == i and y == j for (x,y), __ in moves[:-1]):
        return False

    # Check coordinates
    if i >= 10 or i < 0 or j >= len(result) // 10 or j < 0:
        return False
    elif result[i+j*10] == '3':
        return False

    return True

# Return true if 2 is reached
def find_goal(result, moves):
    # Unpack
    (i,j), _ = moves[-1]

    if result[i+j*10] == '2':        
        #Print moves
        output = 'Found: '
        for (x,y), _ in moves:
            output += " "+_
        print(output)       
        #Print grid
        print_grid(result, moves[1:-1])

        return True
        
    return False

# Return new position and which movement was done.
def move(pos, dir):
    (x, y), _ = pos
    
    if dir == 'Move Left':
        x -= 1
    elif dir == 'Move Right':
        x += 1
    elif dir == 'Move Up':
        y -= 1
    elif dir == 'Move Down':
        y += 1
    
    return (x, y), dir 

nums = queue.Queue()
result = read_to_grid()

# Find the starting position
for x, pos in enumerate(result):
    if pos == '1':
        start = x

add = [((start % 10, start // 10),'')]

while not find_goal(result, add):
    if not nums.empty():
        add = nums.get()
    for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
        put = add + [move(add[-1],j)]
        if valid(result, put):
            nums.put(put)
Brandom
  • 26
  • 5