Advertisement
cjburkey01

Untitled

Mar 15th, 2021
1,054
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.03 KB | None | 0 0
  1. import heapq
  2. from math import sqrt
  3.  
  4.  
  5. # A node within the space to search
  6. class Node:
  7.     # Pos is a 2-value array representing the [row,col] position of this node.
  8.     # Parent is another instance of node.
  9.     # Path cost is the cost to reach to reach this node.
  10.     #   Setting path cost to zero means the total cost can be sorted such that
  11.     #   the algorithm is a greedy search algorithm rather than A*.
  12.     # Heuristic is the estimated benefit of using this node
  13.     def __init__(self, pos, parent, path_cost, heuristic):
  14.         self.pos = pos
  15.         self.parent = parent
  16.         self.path_cost = path_cost
  17.         self.heuristic = heuristic
  18.         self.total_cost = path_cost + heuristic
  19.  
  20.     # Determine if this node is the same as another
  21.     # (If we don't do this, python will count two different objects as
  22.     # different nodes even if they share the same location; this breaks our
  23.     # checks for whether a node has already been visited)
  24.     def __eq__(self, other):
  25.         if other.pos is not None and len(other.pos) == 2:
  26.             return other.pos[0] == self.pos[0] and other.pos[1] == self.pos[1]
  27.  
  28.     # Determine if the other node has a smaller or larger cost than another
  29.     def __lt__(self, other):
  30.         if other is not None:
  31.             return self.total_cost < other.total_cost
  32.  
  33.  
  34. def getNeighbors(location, cost_grid):
  35.     # Get row count
  36.     rows = len(cost_grid)
  37.  
  38.     # If there are no rows, return an empty list
  39.     if rows < 1:
  40.         return []
  41.  
  42.     # Get the columns count
  43.     cols = len(cost_grid[0])
  44.  
  45.     # If there are no columns, return an empty list
  46.     if cols < 1:
  47.         return []
  48.  
  49.     # Make sure the position is valid and within the provided grid
  50.     if len(location) != 2\
  51.             or location[0] < 0\
  52.             or location[0] >= rows\
  53.             or location[1] < 0\
  54.             or location[1] >= cols:
  55.         return []
  56.  
  57.     # Define neighbors list
  58.     neighbors = []
  59.  
  60.     # Add up neighbor
  61.     if location[0] > 0 and cost_grid[location[0] - 1][location[1]] != 0:
  62.         neighbors.append([location[0] - 1, location[1]])
  63.     # Add down neighbor
  64.     if location[0] < rows - 1 and cost_grid[location[0] + 1][location[1]] != 0:
  65.         neighbors.append([location[0] + 1, location[1]])
  66.     # Add left neighbor
  67.     if location[1] > 0 and cost_grid[location[0]][location[1] - 1] != 0:
  68.         neighbors.append([location[0], location[1] - 1])
  69.     # Add right neighbor
  70.     if location[1] < cols - 1 and cost_grid[location[0]][location[1] + 1] != 0:
  71.         neighbors.append([location[0], location[1] + 1])
  72.  
  73.     # Return neighbors list
  74.     return neighbors
  75.  
  76.  
  77. # Expand a node into its neighbors, put them into the frontier, and mark the
  78. # node as visited
  79. def expandNode(node, end_pos, cost_grid, closed_list, open_list, euclidean):
  80.     # Get possible neighbors for this node's position
  81.     for neighbor in getNeighbors(node.pos, cost_grid):
  82.         path_cost = cost_grid[neighbor[0]][neighbor[1]]
  83.         if path_cost == 0:
  84.             continue
  85.  
  86.         c = Node(neighbor, node, path_cost, calc_heuristic(neighbor, end_pos, euclidean))
  87.  
  88.         for n in open_list:
  89.             if n.pos == neighbor:
  90.                 new_g = path_cost
  91.                 old_g = n.path_cost
  92.                 if new_g < old_g:
  93.                     open_list.remove(n)
  94.                     heapq.heapify(open_list)
  95.                     heapq.heappush(open_list, c)
  96.                     if c in closed_list:
  97.                         closed_list.remove(c)
  98.                         heapq.heapify(closed_list)
  99.                 break
  100.  
  101.         if c not in closed_list:
  102.             heapq.heappush(open_list, c)
  103.  
  104.  
  105. # The grid values must be separated by spaces, e.g.
  106. # 0 0 0 0 0
  107. # 0 1 1 1 0
  108. # 0 1 2 1 0
  109. # 0 0 0 0 0
  110. # Returns a 2D list of 1s and 0s
  111. def readGrid(filename):
  112.     # Initialize grid to empty list
  113.     cost_grid = []
  114.     start_pos = [1, 1]
  115.     end_pos = [1, 1]
  116.  
  117.     # Open the file
  118.     with open(filename) as f:
  119.         # Read each line of the file
  120.         for (i, line) in enumerate(f.readlines()):
  121.             # Split row into separate columns (by whitespace)
  122.             cols = line.split()
  123.  
  124.             # Create an empty row for the grid
  125.             row = []
  126.  
  127.             # Loop through the cells in this row
  128.             for (j, cell) in enumerate(cols):
  129.                 # Set the cost for this row to 1 by default
  130.                 cost = 1
  131.  
  132.                 # Check if this cell is the start cell (give it a cost of 1 for simplicity)
  133.                 if cell == 'S':
  134.                     start_pos = [i, j]
  135.                 # Check if this cell is the goal cell (give it a cost of 1 for simplicity)
  136.                 elif cell == 'G':
  137.                     end_pos = [i, j]
  138.                 else:
  139.                     cost = int(cell)
  140.  
  141.                 # Add this cost to the row
  142.                 row.append(cost)
  143.  
  144.             # Add the row
  145.             cost_grid.append(row)
  146.  
  147.     # Return the grid, start, and end positions
  148.     return cost_grid, start_pos, end_pos
  149.  
  150.  
  151. # Writes a 2D list of 1s and 0s with spaces in between each character
  152. # 1 1 1 1 1
  153. # 1 0 0 0 1
  154. # 1 0 0 0 1
  155. # 1 1 1 1 1
  156. def outputGrid(filename_str, cost_grid, start_pos, goal_pos, path):
  157.     # Open file
  158.     with open(filename_str, 'w') as f:
  159.         print('Outputting grid: Goal: ' + str(goal_pos[0])
  160.               + ', ' + str(goal_pos[1])
  161.               + ' | Grid size: ' + str(len(cost_grid))
  162.               + 'x' + str(len(cost_grid[0])))
  163.  
  164.         # Mark the start and goal points
  165.         cost_grid[start_pos[0]][start_pos[1]] = 'S'
  166.         cost_grid[goal_pos[0]][goal_pos[1]] = 'G'
  167.  
  168.         # Mark intermediate points with *
  169.         for i, p in enumerate(path):
  170.             if 0 < i < len(path) - 1:
  171.                 cost_grid[p[0]][p[1]] = '*'
  172.  
  173.         # Write the grid to a file
  174.         for r, row in enumerate(cost_grid):
  175.             for c, col in enumerate(row):
  176.                 # Don't add a ' ' at the end of a line
  177.                 if c < len(row) - 1:
  178.                     f.write(str(col) + ' ')
  179.                 else:
  180.                     f.write(str(col))
  181.  
  182.             # Don't add a '\n' after the last line
  183.             if r < len(cost_grid) - 1:
  184.                 f.write("\n")
  185.  
  186.  
  187. def setPath(current, path):
  188.     # Keep track of current looping node
  189.     node = current
  190.  
  191.     # Loop through parents to beginning
  192.     while node is not None:
  193.         # Add this node to the path
  194.         path.append(node.pos)
  195.  
  196.         # Assign current node to parent
  197.         node = node.parent
  198.  
  199.     # Flip the path around so the first element is the beginning
  200.     path.reverse()
  201.  
  202.  
  203. # Calculate the estimated cost from the provided node to the end position
  204. # If `euclidean` is False, manhattan distance is used instead
  205. def calc_heuristic(node_pos, end_pos, euclidean):
  206.     if euclidean:
  207.         return sqrt((node_pos[0] - end_pos[0])**2 + (node_pos[1] - end_pos[1])**2)
  208.     return node_pos[0] - end_pos[0] + node_pos[1] - end_pos[1]
  209.  
  210.  
  211. # Perform a search using A*
  212. # If `euclidean` is False, manhattan distance is used instead
  213. def informedSearch(cost_grid, start_pos, end_pos, euclidean=False):
  214.     # Initialize search
  215.     open_list = []
  216.     closed_list = []
  217.  
  218.     # Add the starting node to the queue
  219.     heapq.heappush(open_list, Node(start_pos, None, 0, calc_heuristic(start_pos, end_pos, euclidean)))
  220.  
  221.     # Loop until we reach the goal
  222.     while len(open_list) > 0:
  223.         # Pull a new node from open_list
  224.         current = heapq.heappop(open_list)
  225.  
  226.         # Check if we're at the goal position
  227.         if current.pos == end_pos:
  228.             # Create path
  229.             path = []
  230.             setPath(current, path)
  231.  
  232.             # Return path
  233.             return path
  234.  
  235.         # Add current node to the closed list
  236.         closed_list.append(current)
  237.  
  238.         # Push traversable neighbors to `open_list`
  239.         expandNode(current, end_pos, cost_grid, closed_list, open_list, euclidean)
  240.  
  241.     # No path was found!
  242.     return []
  243.  
  244.  
  245. def print_grid(grid):
  246.     for r in range(len(grid)):
  247.         for c in grid[r]:
  248.             print(str(c), end=' ')
  249.         print('')
  250.  
  251.  
  252. def __main__():
  253.     # Determine whether to use manhattan or euclidean distance
  254.     getting_input = True
  255.     euclidean = None
  256.     while getting_input:
  257.         euclidean = input('Use Euclidean or Manhattan distance? (E/M): ')
  258.         if euclidean == 'E':
  259.             euclidean = True
  260.         elif euclidean == 'M':
  261.             euclidean = False
  262.         else:
  263.             print('Please enter \"E\" or \"M\"')
  264.             continue
  265.  
  266.         getting_input = False
  267.  
  268.     # Read the grid in, perform the search, and output the path
  269.     print('Loading grid')
  270.     cost_grid, start_pos, end_pos = readGrid('maze_in.txt')
  271.     print_grid(cost_grid)
  272.     print('Solving path')
  273.     solved_path = informedSearch(cost_grid, start_pos, end_pos, euclidean)
  274.     if len(solved_path) < 1:
  275.         print('Failed to solve path!')
  276.     else:
  277.         print('Writing solved maze')
  278.         outputGrid("maze_out.txt", cost_grid, start_pos, end_pos, solved_path)
  279.  
  280.  
  281. __main__()
  282.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement