Skip to content

Instantly share code, notes, and snippets.

@mikeweber
Created January 24, 2018 19:14
Show Gist options
  • Save mikeweber/e8ffd0e49056f9b79945aefad2c22e12 to your computer and use it in GitHub Desktop.
Save mikeweber/e8ffd0e49056f9b79945aefad2c22e12 to your computer and use it in GitHub Desktop.
A-star implementation in Elixir
defmodule Astar do
# When the frontier is empty, we're done
def traverse_graph({[], came_from}, _, _), do: {[], came_from}
# Otherwise get the next lowest scored frontier and visit it
def traverse_graph({frontier, came_from}, graph, target) do
{current_node_name, frontier} = PriorityQueue.get(frontier)
current_node = graph |> Graph.get_node(current_node_name)
visit({frontier, came_from}, graph, target, current_node)
end
# When the origin and target are the same, we're done
def visit(state, _, target, target), do: state
# Visit the node
def visit(state, graph, %{ entity: target_entity } = target, %GraphNode{ adjacents: adjacents, name: current_name }) do
adjacents
|> Enum.reduce(state, fn({next_name, {next_weight, next_entity}}, {frontier, came_from})->
new_cost = (came_from[current_name] |> elem(0)) + next_weight
prev_node = came_from[next_name]
if prev_node && elem(prev_node, 0) <= new_cost do
# When this node has already been visited and it already has a lower cost,
# make no changes to the frontier
{frontier, came_from}
else
# If the node is unvisited, or has a higher cost than the new cost,
# override the value in the came_from map with the new cost and
# expand the frontier to include the current neighbor with the new
# cost plus heuristic
{
frontier |> PriorityQueue.add({new_cost + heuristic(target_entity, next_entity), next_name}),
came_from |> Map.put(next_name, {new_cost, current_name})
}
end
end)
|> traverse_graph(graph, target)
end
# When an adjacent node is not a part of the graph, ignore it
def visit(state, _, _, nil), do: state
# Return the distance between two points
def heuristic(%{ x: x1, y: y1 }, %{ x: x2, y: y2 }) do
(:math.pow(x1 - x2, 2) + :math.pow(y1 - y2, 2)) |> :math.sqrt
end
end
defmodule PriorityQueue do
def add(queue, { weight, _ } = weighted_node) do
loc = queue |> Enum.find_index(fn({ i_weight, _ }) ->
i_weight < weight
end)
queue |> List.insert_at((loc || 0), weighted_node)
end
def get(queue) do
{ queue |> List.last |> elem(1), queue |> List.delete_at(-1) }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment