Python A* pathfinding, what is this line doing? - python

I am reviewing this stack overflow post
Python - Speed up an A Star Pathfinding Algorithm
I am trying to determine what the line for tile in graph[current]: represents. Namely what graph[] represents. I feel like graph should represent the entire grid, but I second guess this because we are giving current as argument to the [] operator on graph, so it has to be returning something, but im not sure what it should be. Maybe the tiles that we can travel to that are directly adjacent to current?
Also what does this syntax mean current = heapq.heappop(openHeap)[1]?
import heapq
def aStar(self, graph, current, end):
openSet = set()
openHeap = []
closedSet = set()
def retracePath(c):
path = [c]
while c.parent is not None:
c = c.parent
path.append(c)
path.reverse()
return path
openSet.add(current)
openHeap.append((0,current))
while openSet:
current = heapq.heappop(openHeap)[1]
if current == end:
return retracePath(current)
openSet.remove(current)
closedSet.add(current)
for tile in graph[current]:
if tile not in closedSet:
tile.H = (abs(end.x-tile.x)+abs(end.y-tile.y))*10
if tile not in openSet:
openSet.add(tile)
heapq.heappush(openHeap, (tile.H,tile))
tile.parent = current
return []

I believe the graph variable is a dict of some sort where the key is the current tile, and the value is a list of all the valid neighboring tiles. That way, every node in the graph is easily accessible via simple dict lookup.
The pseudocode on Wikipedia the author linked to in the original post supports this hypothesis -- the functionally equivalent line is listed as for each neighbor in neighbor_nodes(current)
What the line current = heapq.heappop(openHeap)[1] is doing is returning the literal tile object. If you observe the lines openHeap.append((0,current)) and heapq.heappush(openHeap, (tile.H,tile)), you can observe that the author is adding a tuple of two elements to openHeap where the first element is the heuristic, and the second element is the literal tile object.
Therefore, the line current = heapq.heappop(openHeap)[1] is identical to writing:
temp = heapq.heappop(openHeap)
current = temp[1]
...or to writting:
h, current = heapq.heappop(openHeap)
What the heaqpq.heappop() function itself is doing is returning the smallest element in the heap. Presumably, it's using the first element in the tuple to index, and so will return the open tile with the smallest heuristic as a cheap O(1) operation.

Related

Create a matrix of numbers and starting point for use in traversal/search programs

I am trying to create a python class that establishes a grid of n x n numbers and then uses that grid for the purpose of depth-first and breadth-first search/traversal, based on a starting point in the grid. The variables that would be defined when calling the class as part of a test are the starting row + the starting column (defining the start point) and the size of the row + size of the column (defining the two dimensional grid space). the class starts like this:
class Graph:
def __init__(self, start_row: int, start_col: int, size_row: int, size_col: int):
#constructor
self.graph = [[0] * size_col for _ in range(size_row)]
self.bfs(start_row, start_col)
I was able to create the matrix based on what the inputs would be included while calling the object, which would look something like this:
def test_graph():
g = Graph(3, 5, 8, 9)
The remainder of the code for the Breadth-First Search algorithm looks like this, in the same class built as another method:
BFS function
def bfs(self,s): # this is our function using params as
# visited nodes, the graph, and the node
# initialize all vertices as not visited
visited = [False] * (max(self.graph)+1)
# create a BFS queue
queue = []
# mark the source node as visited and enqueue it
queue.append(s)
visited[s] = True
# while loop to keep the routine running until nothing
#left to visit
while queue:
# remove a vertex from queue
s = queue.pop()
# get all adjacent vertices of the last considered
#vertex; if adjacency not visited, mark
# ~ it visited and enqueue
for i in self.graph[s]:
if visited[i] == False:
queue.append[i]
visited[i] = True
The problem I am having is that my object is returning arrays of all zeroes as opposed to the outcome of the BFS. Am I missing something in my Class that is causing the object to not execute the BFS method on the given Class inputs?
Multiple issues in this
Calling self.bfs() with 2 arguments (row and col) while the definition has only one (s)
max(self.graph) would return a list, you can't initialize visited with that.
s=queue.pop() will actually make the function behave as dfs and not bfs. .pop() pops from the end of the list. If you have [1,2,3] and call pop(), 3 will be returned and behave as a stack. To make it behave like a queue, queue.pop(0) should be used.
Not sure what the logic is behind using a single dimension visited array and what s refers to.

Rotate a linked list k times (Python)

While practicing for my final in Python programming I ran into this question "def rotaten" of rotating k times. The problem says that k can range from 0 to any positive integer number (even greater than list size, if k < 0 raise ValueError
and it must execute in O( (n-k)%n ) where n is the length of the list. It also has the following warnings:
WARNING: DO NOT call .rotate() k times !!!!
WARNING: DO NOT try to convert whole linked list to a python list
WARNING: DO NOT swap node data or create nodes
The problem is that I'm not understanding the solution given. Is there an easier way to solve this problem? Thank you in advance
class Node:
def __init__(self,initdata):
self._data = initdata
self._next = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def set_data(self,newdata):
self._data = newdata
def set_next(self,newnext):
self._next = newnext
class LinkedList:
def rotaten(self, k):
if k < 0:
raise ValueError
if self._size > 1:
m = k % self._size
if m > 0 and m < self._size:
current = self._head
for i in range(self._size - m - 1):
current = current.get_next()
chain_b = current.get_next()
old_head = self._head
old_last = self._last
self._last = current
self._last.set_next(None)
self._head = chain_b
old_last.set_next(old_head)
The easiest ways are forbidden
warning forbids implementing the singular rotation, then calling it repeatedly
warning forbids using native python structure - python has list built-in in basic collections list(), which can be then transformed to a deque, which can then be rotated by popping from the end and inserting the same node to the beginning.
warning is preventing you from making your life easier by creating some other nodes or worse - copying data. (copying data from one node to another would remove any advantage you had from storing the data into the lists in the first place)
The example solution is basically this:
Take the current first node(head of the list) and the tail node which is stored in the List structure. (the bookkeeping of the example list consists of holding the head and tail, the rest is done in the nodes themselves)
Find the k-th node - so that you rotate the whole list at once. This is where the list needs to be cut.
Add the last node as the new head, doing all the necessary reference-linking and unlinking. (Which is I guess the point of this question - to test if you understand the references. In C these would be pointers, python has the references implicitly.)
So as far as the linked lists go, this is the most straightforward solution with the requested O((n-k)%n) complexity.
Good luck with your pointers :-).

Can my code be classified as a depth first search?

I wrote code for a DFS after reading about what it is but not actually seeing the code. I did this to challenge myself (I always have believed that to learn something new you must always first challenge yourself). The thing is after I wrote my code, I compared my implementation to the one in the book I read it in (Introduction to the Design and Analysis of Algorithms - A. Levitin) and it is completely different. So now I am wondering well it works as intended... is it still a DFS?
I made the implementation to solve a maze. I will give a rundown on my code and also upload the code here (Some people hate reading other people's code while others do.)
Algorithm (What I understood and did):
Convert maze into a graph/map
Set start position as current node and run loop in which...
I choose one of the adjacent nodes as the next current node and do this until I stumble upon a dead end. Also I am adding each node I pass through into a list that acts as my stack.
Once I am at a dead end, I keep poping items from the stack and each time I pop, I check if it has adjacent nodes that have not been visited.
Once I have found an unvisited adjacent node, we continue the entire process from step 3.
We do this until current node is the end position.
Then I just retrace my way back through the stack.
Here is my code:
# Depth First Search implementation for maze...
# from random import choice
from copy import deepcopy
import maze_builderV2 as mb
order = 10
space = ['X']+['_' for x in range(order)]+['X']
maze = [deepcopy(space) for x in range(order)]
maze.append(['X' for x in range(order+2)])
maze.insert(0, ['X' for x in range(order+2)])
finalpos = (order, order)
pos = (1, 1)
maze[pos[0]][pos[1]] = 'S' # Initializing a start position
maze[finalpos[0]][finalpos[1]] = 'O' # Initializing a end position
mb.mazebuilder(maze=maze)
def spit():
for x in maze:
print(x)
spit()
print()
mazemap = {}
def scan(): # Converts raw map/maze into a suitable datastructure.
for x in range(1, order+1):
for y in range(1, order+1):
mazemap[(x, y)] = []
t = [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
for z in t:
if maze[z[0]][z[1]] == 'X':
pass
else:
mazemap[(x, y)].append(z)
scan()
path = [pos] # stack
impossible = False
while path[-1] != finalpos:
curpos = path[-1]
i = 0
while i < len(mazemap[curpos]):
if mazemap[curpos][i] in path:
del mazemap[curpos][i]
else:
i += 1
nextpos = None
if mazemap[curpos] == []:
while nextpos == None:
try:
wrongpos = path.pop(-1)
if mazemap[wrongpos] == []:
pass
else:
path.append(wrongpos)
# nextpos = choice(mazemap[wrongpos])
nextpos = mazemap[wrongpos][-1]
mazemap[wrongpos].remove(nextpos)
except IndexError:
impossible = True
break
else:
# nextpos = choice(mazemap[curpos])
nextpos = mazemap[curpos][-1]
if impossible:
break
path.append(nextpos)
if not impossible:
for x in path:
if x == pos or x == finalpos:
pass
else:
maze[x[0]][x[1]] = 'W'
else:
print("This maze not solvable, Blyat!")
print()
spit()
As always, I greatly appreciate your suggestions!
Your algorithm looks DFS to me. DFS means exploring the path as deep as possible, backtrack to the previous node only if there is no solution and your algorithm works in a similar way by popping nodes from the stack. You just mimic the recursion stack using your own stack so it looks quite different from the standard solution.
Essentially, all recursive algorithms can be simulated using stack and loop. But most of the time doing this will make the algorithm much less readable. To tackle a difficult problem, I think the usual way to do it is to first come up with the recursive solution. After making sure the recursive solution is bug-free, then start implementing the iterative version using stack if you care a lot about the efficiency.
Other Suggestion:
if mazemap[curpos][i] in path: is a O(n) operation since path is a normal list. Consider using a separate hash set to store visited nodes and use the set to check repetition instead to make it O(1).

Breadth search algorithm implementation

I wrote a python code for Breadth search algorithm for 8 puzzle probelm. The states of the game are coded as a list of lists (two dimensional list). The goal is to reach [[1,2,3],[8,0,4],[7,6,5]] from a given state. The code output the path from a given state to the goal. It works fine but for this particular case [[0,1,2],[7,8,3],[6,5,4]] does not give the whole path rather it gives the last three states in the path.
Would you please try to point out the place of the bugs. Here is the code. Note The program starts from the statement:
"""
This program solves the 8 puzzel problems using Breadth First Search Algorithm.
"""
import copy
def successor(astate):
"""
This function takes an instance of the 8 puzzel problem and generates all the legal successors.
"""
up = copy.deepcopy(astate)
"""
important note:
I use [:] and list() to copy lists but it was not working fine, hence deecopy is used instead.
"""
down = copy.deepcopy(astate)
left = copy.deepcopy(astate)
right = copy.deepcopy(astate)
successors = []
for i in range(3):
for j in range(3):
if astate[i][j] == 0:
row = i
col = j
if row != 0:
dummy = up[row][col]
up[row][col] = up[row -1][col]
up[row-1][col] = dummy
successors.append(up)
if row != 2:
dummy = down[row][col]
down[row][col] = down[row+1][col]
down[row+1][col] = dummy
successors.append(down)
if col != 2:
dummy = right[row][col]
right[row][col] = right[row][col+1]
right[row][col+1] = dummy
successors.append(right)
if col != 0:
dummy = left[row][col]
left[row][col] = left[row][col-1]
left[row][col-1] = dummy
successors.append(left)
return successors
def puzzle(astate):
"""
This function takes a given instance of the 8 puzzel problem and returns the path to the goal where the goal is defined as below.
"""
goal = [[1,2,3],[8,0,4],[7,6,5]] #The goal state.
generation = [astate] #Nodes generated at each level.
tracking = {} #Track the path to the goal.
path = [] #The path from the root to the goal.
parent = generation.pop(0) #Takes the first element of the list!!
successors = successor(parent) #Generate successors.
key = str(parent) #keys odictionaries must be hashable and mutable. Lists are illegal keys.
tracking[key] = successors #Associate successors with their parent.
for asuccessor in successors:
generation.append(asuccessor) #Generate the first level.
if goal in generation: #if the goal is among the successors returns the path to it.
path.insert(0, key)
path.insert(0, goal)
return path
else:
while generation != []: #keep searching!
parent = generation.pop(0)
successors = successor(parent) #generate successors
key = str(parent)
tracking[key] = successors
if goal in [astate for astate in successors]: #if the goal is among the successors backtrack its path.
path.insert(0, str(goal)) #Just because path contains states as strings!!
path.insert(0, key)
for key in tracking.keys():
for value in tracking.get(key):
if str(parent) == str(value): #If the current (parent) is among the values of (key) then (key) is its parent.
path.insert(0, key)
parent = key
break
return path
else: #keep searching
for asuccessor in successors:
if asuccessor not in generation: #If the current successors is already generated do not add it.
if str(asuccessor) not in tracking.keys(): #If the successor is a previous parent do not add it.
generation.append(asuccessor)
return

Python - Calculating Distance in Dijkstra

I am having some trouble in determining the distance of each node from the start node, or rather getting any information back at all.
I get no output from my function, attached in the following link.
#Values to assign to each node
class Node:
distFromSource = infinity
previous = invalid_node
visited = False
#for each node assign default values
def populateNodeTable(network):
nodeTable = []
index = 0
f = open('network.txt', 'r')
for line in f:
node = map(int, line.split(','))
nodeTable.append(Node())
print "The previous node is " ,nodeTable[index].previous
print "The distance from source is " ,nodeTable[index].distFromSource
index +=1
nodeTable[startNode].distFromSource = 0
return nodeTable
#calculate the distance of each node from the start node
def tentativeDistance(currentNode, nodeTable):
nearestNeighbour = []
for currentNode in nearestNeighbour:
currentDistance == currentNode.distFromSource + [currentNode][nearestNeighbour] #gets current distance from source
print "The current distance"
if currentDistance != 0 & currentNode.distFromSource < Node[currentNode].distFromSource:
nodeTable[currentNode].previous = currentNode
nodeTable[currentNode].length = currentDistance
nodeTable[currentNode].visited = True
nodeTable[currentNode] +=1
nearestNeighbour.append(currentNode)
for currentNode in nearestNeighbour:
print nearestNeighbour
return nearestNeighbour
My logic is, at least in my mind, correct; however, I don't get as much as an error message when the code is run.
You're setting nearestNeighbour to be an empty list, and then you're looping over it with for currentNode in nearestNeighbour -- which does nothing, because the list is empty -- and then you're returning from the function.
(I assume tentativeDistance is the function you're calling and seeing nothing from.)
You should rethink your algorithm design. Try looking up a pseudocode definition of Dijkstra's algorithm and implementing that in Python. In particular, you should think about the control flow in your program.
You might want to have a look at this cookbook recipe for a Python implementation of Dijkstra and see if you can understand it.

Categories

Resources