Skip to content

Instantly share code, notes, and snippets.

@abhin4v
Last active November 24, 2022 06:36
Show Gist options
  • Save abhin4v/8172534 to your computer and use it in GitHub Desktop.
Save abhin4v/8172534 to your computer and use it in GitHub Desktop.
A* (A star) search in haskell
import qualified Data.PQueue.Prio.Min as PQ
import qualified Data.HashSet as Set
import qualified Data.HashMap.Strict as Map
import Data.Hashable (Hashable)
import Data.List (foldl')
import Data.Maybe (fromJust)
astarSearch :: (Eq a, Hashable a) => a -> (a -> Bool) -> (a -> [(a, Int)]) -> (a -> Int) -> Maybe (Int, [a])
astarSearch startNode isGoalNode nextNodeFn heuristic =
astar (PQ.singleton (heuristic startNode) (startNode, 0))
Set.empty (Map.singleton startNode 0) Map.empty
where
astar pq seen gscore tracks
| PQ.null pq = Nothing
| isGoalNode node = Just (gcost, findPath tracks node)
| Set.member node seen = astar pq' seen gscore tracks
| otherwise = astar pq'' seen' gscore' tracks'
where
(node, gcost) = snd . PQ.findMin $ pq
pq' = PQ.deleteMin pq
seen' = Set.insert node seen
successors =
filter (\(s, g, _) -> not (Set.member s seen') &&
(not (s `Map.member` gscore) || g < (fromJust . Map.lookup s $ gscore)))
$ successorsAndCosts node gcost
pq'' = foldl' (\q (s, g, h) -> PQ.insert (g + h) (s, g) q) pq' successors
gscore' = foldl' (\m (s, g, _) -> Map.insert s g m) gscore successors
tracks' = foldl' (\m (s, _, _) -> Map.insert s node m) tracks successors
successorsAndCosts node gcost = map (\(s, g) -> (s, gcost + g, heuristic s)) . nextNodeFn $ node
findPath tracks node = if Map.member node tracks
then findPath tracks (fromJust . Map.lookup node $ tracks) ++ [node]
else [node]
import qualified Data.PQueue.Prio.Min as PQ
import qualified Data.HashSet as Set
import qualified Data.HashMap.Strict as Map
import Data.Hashable (Hashable)
import Data.List (foldl')
import Data.Maybe (fromJust)
-- A* search: Finds the shortest path from a start node to a goal node using a heuristic function.
astarSearch :: (Eq a, Hashable a) =>
a -- startNode: the node to start the search from
-> (a -> Bool) -- isGoalNode: a function to test if a node is the goal node
-> (a -> [(a, Int)]) -- nextNodeFn: a function which calculates the next nodes for a current node
-- along with the costs of moving from the current node to the next nodes
-> (a -> Int) -- heuristic: a function which calculates the (approximate) cost of moving
-- from a node to the nearest goal node
-> Maybe (Int, [a]) -- result: Nothing is no path is found else
-- Just (path cost, path as a list of nodes)
astarSearch startNode isGoalNode nextNodeFn heuristic =
astar (PQ.singleton (heuristic startNode) (startNode, 0))
Set.empty (Map.singleton startNode 0) Map.empty
where
-- pq: open set, seen: closed set, tracks: tracks of states
astar pq seen gscore tracks
-- If open set is empty then search has failed. Return Nothing
| PQ.null pq = Nothing
-- If goal node reached then construct the path from the tracks and node
| isGoalNode node = Just (gcost, findPath tracks node)
-- If node has already been seen then discard it and continue
| Set.member node seen = astar pq' seen gscore tracks
-- Else expand the node and continue
| otherwise = astar pq'' seen' gscore' tracks'
where
-- Find the node with min f-cost
(node, gcost) = snd . PQ.findMin $ pq
-- Delete the node from open set
pq' = PQ.deleteMin pq
-- Add the node to the closed set
seen' = Set.insert node seen
-- Find the successors (with their g and h costs) of the node
-- which have not been seen yet
successors =
filter (\(s, g, _) ->
not (Set.member s seen') &&
(not (s `Map.member` gscore)
|| g < (fromJust . Map.lookup s $ gscore)))
$ successorsAndCosts node gcost
-- Insert the successors in the open set
pq'' = foldl' (\q (s, g, h) -> PQ.insert (g + h) (s, g) q) pq' successors
gscore' = foldl' (\m (s, g, _) -> Map.insert s g m) gscore successors
-- Insert the tracks of the successors
tracks' = foldl' (\m (s, _, _) -> Map.insert s node m) tracks successors
-- Finds the successors of a given node and their costs
successorsAndCosts node gcost =
map (\(s, g) -> (s, gcost + g, heuristic s)) . nextNodeFn $ node
-- Constructs the path from the tracks and last node
findPath tracks node =
if Map.member node tracks
then findPath tracks (fromJust . Map.lookup node $ tracks) ++ [node]
else [node]
@george-lukas
Copy link

Hello! How can I test it?

@OffensiveK
Copy link

--Example of Use

import AStar
import Data.List
import Data.Hashable
import Data.Maybe

data Node = A | B | C | D | E | F | G deriving (Read,Show,Eq,Enum)

instance Hashable Node where
hashWithSalt s x = s + (fromJust $ elemIndex x (enumFrom A))

cost A = [(D,2),(C,3),(B,9)]
cost B = [(A,9),(C,2)]
cost C = [(A,3),(B,2),(G,5)]
cost D = [(A,2),(F,2),(E,4)]
cost E = [(D,4),(F,2),(G,4)]
cost F = [(D,3),(E,2),(G,9)]
cost _ = []

main = do
putStrLn $ show $ astarSearch B (==E) cost (const 0)

@ekenberg
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment