Created
January 24, 2018 19:14
-
-
Save mikeweber/e8ffd0e49056f9b79945aefad2c22e12 to your computer and use it in GitHub Desktop.
A-star implementation in Elixir
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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