A* search algorithm: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|Python}}: added zkl header)
(→‎{{header|zkl}}: added code)
Line 121: Line 121:


=={{header|zkl}}==
=={{header|zkl}}==
{{trans|Python}}
<lang zkl></lang>
<lang zkl> // we use strings as hash keys: (x,y)-->"x,y", keys are a single pair
<lang zkl></lang>
fcn toKey(xy){ xy.concat(",") }

fcn AStarSearch(start,end,graph){
G:=Dictionary(); # Actual movement cost to each position from the start position
F:=Dictionary(); # Estimated movement cost of start to end going via this position
#Initialize starting values
kstart:=toKey(start);
G[kstart]=0;
F[kstart]=graph.heuristic(start,end);
closedVertices,openVertices,cameFrom := List(),List(start),Dictionary();
while(openVertices){
# Get the vertex in the open list with the lowest F score
current,currentFscore := Void, Void;
foreach pos in (openVertices){
kpos:=toKey(pos);
if(current==Void or F[kpos]<currentFscore)
currentFscore,current = F[kpos],pos;
# Check if we have reached the goal
if(current==end){ # Yes! Retrace our route backward
path,kcurrent := List(current),toKey(current);
while(current = cameFrom.find(kcurrent)){
path.append(current);
kcurrent=toKey(current);
}
return(path.reverse(),F[toKey(end)]) # Done!
}

# Mark the current vertex as closed
openVertices.remove(current);
if(not closedVertices.holds(current)) closedVertices.append(current);
# Update scores for vertices near the current position
foreach neighbor in (graph.get_vertex_neighbors(current)){
if(closedVertices.holds(neighbor))
continue; # We have already processed this node exhaustively
kneighbor:=toKey(neighbor);
candidateG:=G[toKey(current)] + graph.move_cost(current, neighbor);
if(not openVertices.holds(neighbor))
openVertices.append(neighbor); # Discovered a new vertex
else if(candidateG>=G[kneighbor])
continue; # This G score is worse than previously found
# Adopt this G score
cameFrom[kneighbor]=current;
G[kneighbor]=candidateG;
F[kneighbor]=G[kneighbor] + graph.heuristic(neighbor,end);
}
}
} // while
throw(Exception.AssertionError("A* failed to find a solution"));
}</lang>
<lang zkl>class [static] AStarGraph{ # Define a class board like grid with barriers
var [const] barriers =
T( T(3,2),T(4,2),T(5,2), // T is RO List
T(5,3),
T(2,4), T(5,4),
T(2,5), T(5,5),
T(2,6),T(3,6),T(4,6),T(5,6) );
fcn heuristic(start,goal){ // (x,y),(x,y)
# Use Chebyshev distance heuristic if we can move one square either
# adjacent or diagonal
D,D2,dx,dy := 1,1, (start[0] - goal[0]).abs(), (start[1] - goal[1]).abs();
D*(dx + dy) + (D2 - 2*D)*dx.min(dy);
}
fcn get_vertex_neighbors([(x,y)]){ # Move like a chess king
var moves=Walker.cproduct([-1..1],[-1..1]).walk(); // 8 moves + (0,0)
moves.pump(List,'wrap([(dx,dy)]){
x2,y2 := x + dx, y + dy;
if((dx==dy==0) or x2 < 0 or x2 > 7 or y2 < 0 or y2 > 7) Void.Skip;
else T(x2,y2);
})
}
fcn move_cost(a,b){ // ( (x,y),(x,y) )
if(barriers.holds(b))
return(100); # Extremely high cost to enter barrier squares
1 # Normal movement cost
}
}</lang>
<lang zkl>graph:=AStarGraph;
route,cost := AStarSearch(T(0,0), T(7,7), graph);
println("Route: ", route.apply(fcn(xy){ String("(",toKey(xy),")") }).concat(","));
println("Cost: ", cost);

// graph the solution:
grid:=(10).pump(List,List.createLong(10," ").copy);
foreach x,y in (graph.barriers){ grid[x][y]="#" }
foreach x,y in (route){ grid[x][y]="+" }
grid[0][0] = "S"; grid[7][7] = "E";
foreach line in (grid){ println(line.concat()) }</lang>
{{out}}
{{out}}
<pre>
<pre>
Route: (0,0),(1,1),(2,2),(3,1),(4,0),(5,1),(6,2),(7,3),(7,4),(7,5),(7,6),(7,7)
Cost: 11
S
+
+ ###
+# #
+ # #
+#####
+
++++E
</pre>
</pre>

Revision as of 01:08, 3 January 2017

A* search algorithm is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The A* search algorithm is an extension of Dijkstra's algorithm for route finding that uses heuristics to quickly find an approximate solution.

Task

Consider the problem of finding a route across the diagonal of a chess board-like 8x8 grid. The rows are numbered from 0 to 7. The columns are also numbered 0 to 7. The start position is (0, 0) and the end position is (7, 7). Movement is allow by one square in any direction including diagonals, similar to a king in chess. The standard movement cost is 1. To make things slightly harder, there is a barrier that occupy certain positions of the grid. Moving into any of the barrier positions has a cost of 100. The barrier occupies the positions (2,4),(2,5),(2,6),(3,6),(4,6),(5,6),(5,5),(5,4),(5,3),(5,2),(4,2) and (3,2). A route with the lowest cost should be found using the A* search algorithm (there are multiple optimal solutions with the same total cost).

Print the optimal route in text format, as well as the total cost of the route.

Optionally, draw the optimal route and the barrier positions.

Note: using a heuristic score of zero is equivalent to Dijkstra's algorithm and that's kind of cheating/not really A*!

Python

<lang python>from __future__ import print_function import matplotlib.pyplot as plt

class AStarGraph(object): #Define a class board like grid with two barriers

def __init__(self): self.barriers = [] self.barriers.append([(2,4),(2,5),(2,6),(3,6),(4,6),(5,6),(5,5),(5,4),(5,3),(5,2),(4,2),(3,2)])

def heuristic(self, start, goal): #Use Chebyshev distance heuristic if we can move one square either #adjacent or diagonal D = 1 D2 = 1 dx = abs(start[0] - goal[0]) dy = abs(start[1] - goal[1]) return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)

def get_vertex_neighbours(self, pos): n = [] #Moves allow link a chess king for dx, dy in [(1,0),(-1,0),(0,1),(0,-1),(1,1),(-1,1),(1,-1),(-1,-1)]: x2 = pos[0] + dx y2 = pos[1] + dy if x2 < 0 or x2 > 7 or y2 < 0 or y2 > 7: continue n.append((x2, y2)) return n

def move_cost(self, a, b): for barrier in self.barriers: if b in barrier: return 100 #Extremely high cost to enter barrier squares return 1 #Normal movement cost

def AStarSearch(start, end, graph):

G = {} #Actual movement cost to each position from the start position F = {} #Estimated movement cost of start to end going via this position

#Initialize starting values G[start] = 0 F[start] = graph.heuristic(start, end)

closedVertices = set() openVertices = set([start]) cameFrom = {}

while len(openVertices) > 0: #Get the vertex in the open list with the lowest F score current = None currentFscore = None for pos in openVertices: if current is None or F[pos] < currentFscore: currentFscore = F[pos] current = pos

#Check if we have reached the goal if current == end: #Retrace our route backward path = [current] while current in cameFrom: current = cameFrom[current] path.append(current) path.reverse() return path, F[end] #Done!

#Mark the current vertex as closed openVertices.remove(current) closedVertices.add(current)

#Update scores for vertices near the current position for neighbour in graph.get_vertex_neighbours(current): if neighbour in closedVertices: continue #We have already processed this node exhaustively candidateG = G[current] + graph.move_cost(current, neighbour)

if neighbour not in openVertices: openVertices.add(neighbour) #Discovered a new vertex elif candidateG >= G[neighbour]: continue #This G score is worse than previously found

#Adopt this G score cameFrom[neighbour] = current G[neighbour] = candidateG H = graph.heuristic(neighbour, end) F[neighbour] = G[neighbour] + H

raise RuntimeError("A* failed to find a solution")

if __name__=="__main__": graph = AStarGraph() result, cost = AStarSearch((0,0), (7,7), graph) print ("route", result) print ("cost", cost) plt.plot([v[0] for v in result], [v[1] for v in result]) for barrier in graph.barriers: plt.plot([v[0] for v in barrier], [v[1] for v in barrier]) plt.xlim(-1,8) plt.ylim(-1,8) plt.show()</lang>

Output:
route [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 1), (6, 2), (7, 3), (6, 4), (7, 5), (6, 6), (7, 7)]
cost 11

zkl

Translation of: Python

<lang zkl> // we use strings as hash keys: (x,y)-->"x,y", keys are a single pair fcn toKey(xy){ xy.concat(",") }

fcn AStarSearch(start,end,graph){

  G:=Dictionary(); # Actual movement cost to each position from the start position
  F:=Dictionary(); # Estimated movement cost of start to end going via this position
     #Initialize starting values
  kstart:=toKey(start);
  G[kstart]=0;
  F[kstart]=graph.heuristic(start,end);
  closedVertices,openVertices,cameFrom := List(),List(start),Dictionary();

  while(openVertices){
     # Get the vertex in the open list with the lowest F score
     current,currentFscore := Void, Void;
     foreach pos in (openVertices){
        kpos:=toKey(pos);
        if(current==Void or F[kpos]<currentFscore)

currentFscore,current = F[kpos],pos;

# Check if we have reached the goal if(current==end){ # Yes! Retrace our route backward path,kcurrent := List(current),toKey(current); while(current = cameFrom.find(kcurrent)){ path.append(current); kcurrent=toKey(current); } return(path.reverse(),F[toKey(end)]) # Done! }

# Mark the current vertex as closed openVertices.remove(current); if(not closedVertices.holds(current)) closedVertices.append(current);

# Update scores for vertices near the current position foreach neighbor in (graph.get_vertex_neighbors(current)){ if(closedVertices.holds(neighbor)) continue; # We have already processed this node exhaustively kneighbor:=toKey(neighbor); candidateG:=G[toKey(current)] + graph.move_cost(current, neighbor);

if(not openVertices.holds(neighbor)) openVertices.append(neighbor); # Discovered a new vertex else if(candidateG>=G[kneighbor]) continue; # This G score is worse than previously found

# Adopt this G score cameFrom[kneighbor]=current; G[kneighbor]=candidateG; F[kneighbor]=G[kneighbor] + graph.heuristic(neighbor,end); }

     }
  } // while
  throw(Exception.AssertionError("A* failed to find a solution"));

}</lang> <lang zkl>class [static] AStarGraph{ # Define a class board like grid with barriers

  var [const] barriers =
     T( T(3,2),T(4,2),T(5,2),   // T is RO List

T(5,3), T(2,4), T(5,4), T(2,5), T(5,5), T(2,6),T(3,6),T(4,6),T(5,6) );

  fcn heuristic(start,goal){  // (x,y),(x,y)
  # Use Chebyshev distance heuristic if we can move one square either
  # adjacent or diagonal
     D,D2,dx,dy := 1,1, (start[0] - goal[0]).abs(), (start[1] - goal[1]).abs();
     D*(dx + dy) + (D2 - 2*D)*dx.min(dy);
  }
  fcn get_vertex_neighbors([(x,y)]){      # Move like a chess king
     var moves=Walker.cproduct([-1..1],[-1..1]).walk();  // 8 moves + (0,0)
     moves.pump(List,'wrap([(dx,dy)]){

x2,y2 := x + dx, y + dy; if((dx==dy==0) or x2 < 0 or x2 > 7 or y2 < 0 or y2 > 7) Void.Skip; else T(x2,y2);

     })
  }
  fcn move_cost(a,b){  // ( (x,y),(x,y) )
     if(barriers.holds(b))

return(100); # Extremely high cost to enter barrier squares

     1 # Normal movement cost
  }

}</lang> <lang zkl>graph:=AStarGraph; route,cost := AStarSearch(T(0,0), T(7,7), graph); println("Route: ", route.apply(fcn(xy){ String("(",toKey(xy),")") }).concat(",")); println("Cost: ", cost);

  // graph the solution:

grid:=(10).pump(List,List.createLong(10," ").copy); foreach x,y in (graph.barriers){ grid[x][y]="#" } foreach x,y in (route){ grid[x][y]="+" } grid[0][0] = "S"; grid[7][7] = "E"; foreach line in (grid){ println(line.concat()) }</lang>

Output:
Route: (0,0),(1,1),(2,2),(3,1),(4,0),(5,1),(6,2),(7,3),(7,4),(7,5),(7,6),(7,7)
Cost: 11
S         
 +        
  + ###   
 +#   #   
+ #   #   
 +#####   
  +       
   ++++E