In today’s Programming Praxis, our task is to implement Dijkstra’s shortest path algorithm. Let’s get started, shall we?
Some imports:
import Data.List import qualified Data.List.Key as K import Data.Map ((!), fromList, fromListWith, adjust, keys, Map)
In order make the rest of the algorithm simpler, we convert the list of edges to a map that lists all the neighbors of each vertex.
buildGraph :: Ord a => [(a, a, Float)] -> Map a [(a, Float)] buildGraph g = fromListWith (++) $ g >>= \(a,b,d) -> [(a,[(b,d)]), (b,[(a,d)])]
The algorithm follows the usual steps, albeit in a functional rather than the typical procedural style: start by giving all non-source vertices an infinite distance, then go through all the vertices in order of their distance from the source, relaxing all their neighbors.
dijkstra :: Ord a => a -> Map a [(a, Float)] -> Map a (Float, Maybe a) dijkstra source graph = f (fromList [(v, (if v == source then 0 else 1/0, Nothing)) | v <- keys graph]) (keys graph) where f ds [] = ds f ds q = f (foldr relax ds $ graph ! m) (delete m q) where m = K.minimum (fst . (ds !)) q relax (e,d) = adjust (min (fst (ds ! m) + d, Just m)) e
Getting the shortest path is then simply a matter of tracing the path from the endpoint to the beginning.
shortestPath :: Ord a => a -> a -> Map a [(a, Float)] -> [a] shortestPath from to graph = reverse $ f to where f x = x : maybe [] f (snd $ dijkstra from graph ! x)
A test to see if everything is working properly:
main :: IO () main = do let g = buildGraph [('a','c',2), ('a','d',6), ('b','a',3) ,('b','d',8), ('c','d',7), ('c','e',5) ,('d','e',10)] print $ shortestPath 'a' 'e' g == "ace"
As expected, the shortest route for the given graph is A-C-E.
Tags: algorithm, bonsai, code, dijkstra, Haskell, kata, path, praxis, programming, shortest
March 28, 2011 at 12:59 pm |
I wanted to ask is this code really work?
I tried to run it on HaskellPlatform 2011.2.0.0 and got
“Could not find module ‘Data.List.Key’. i just started to learn this language…what i should do to to fix this mistake.
will be appreciate!!!!!!
March 28, 2011 at 1:03 pm |
You’re missing the utility-ht package (http://hackage.haskell.org/package/utility-ht). If you have cabal installed, just type
cabal install utility-ht
at the command line. You can also do it manually, but it’s more laborious.
March 28, 2011 at 8:13 pm |
what if i have windows?
March 28, 2011 at 10:15 pm |
oh, know it works. grate!
one more question – if i need to find the longest path, what i need to change in this code?
March 28, 2011 at 10:39 pm |
You’ll need a different algorithm entirely. Finding the longest path is the same as finding the shortest path on a graph with negative weights. However, Dijkstra’s algorithm requires that the weights are positive, so it cannot be modified to calculate the longest path. Wikipedia has an algorithm you can use: http://en.wikipedia.org/wiki/Longest_path_problem
March 28, 2011 at 10:52 pm |
I think, i’ll use this algorithm, cause i just started to learn language.
it’s doesn’t matter what algorithm to use for finding the longest path, so i will finish this exercise like you said – with negative weights.
thank you!
March 28, 2011 at 11:10 pm |
one more thing. if i will use negative weight, i need to change data types. what will be better?
March 28, 2011 at 11:47 pm |
i’m asking, because when i put negative weights (
main = do let g = buildGraph [(‘a’,’c’,-2), (‘a’,’d’,-6), (‘b’,’a’,-3),
(‘b’,’d’,-8), (‘c’,’d’,-7), (‘c’,’e’,-5),
(‘d’,’e’,-10)]
the result path was ”.
😦
March 29, 2011 at 12:58 am |
Like I said, the Dijkstra algorithm doesn’t work with negative weights, so you’ll have to use a different algorithm. There’s no change you can make to the code in this exercise that will make it correctly calculate the longest path.
March 29, 2011 at 7:39 am |
ohh…understand 😦
March 29, 2011 at 3:01 pm |
i know, that i can’t ask you, but if you will have some time – can you help me with this algorithm?
will be appreciate, because i’am not sure that i will consult with it for the time i have
March 29, 2011 at 3:22 pm |
As mentioned, the Wikipedia page has an algorithm in pseudocode. Just start by translating that into Haskell. You can use the data structure and buildgraph function from the exercise to create the graphs.
August 21, 2012 at 11:06 pm |
Am I missing something, or you transform a directed graph in a connected graph, removing the directionality in the edges?
In your example, the path from ‘e’ to ‘a’ should return empty list, but it returns ‘ace’ instead.
August 21, 2012 at 11:26 pm |
@Christian: It appears I did. I reread the original exercise and it doesn’t explicitly state the graph is directed. The image is indeed a directed graph, but the original problem statement is about roads between cities, which typically are bidirectional. Apparently (It’s been a year and a half, so I don’t exactly remember my decisions) I chose to treat the graph as a connected one.
August 22, 2012 at 12:07 am |
I see. I like the “very functional” 🙂 way in which you wrote the dijkstra function. Could you provide a suggestion on how to extend it to the directed case?
The algorithm does not work when building the graph via edges
g >>= \(a,b,d) -> [(a,[(b,d)])]