Skip to content

Commit 36da3fb

Browse files
authored
Merge pull request #64 from ntc2/master
Make shortest-path functions safer.
2 parents 5e007d9 + 70d362f commit 36da3fb

File tree

4 files changed

+73
-13
lines changed

4 files changed

+73
-13
lines changed

Data/Graph/Inductive/Internal/RootPath.hs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ first p xss = case filter p xss of
1919
[] -> []
2020
x:_ -> x
2121

22-
-- | Find the first path in a tree that starts with the given node
22+
-- | Find the first path in a tree that starts with the given node.
23+
--
24+
-- Returns an empty list if there is no such path.
2325
findP :: Node -> LRTree a -> [LNode a]
2426
findP _ [] = []
2527
findP v (LP []:ps) = findP v ps
@@ -32,8 +34,13 @@ getPath v = reverse . first (\(w:_)->w==v)
3234
getLPath :: Node -> LRTree a -> LPath a
3335
getLPath v = LP . reverse . findP v
3436

35-
getDistance :: Node -> LRTree a -> a
36-
getDistance v = snd . head . findP v
37+
-- | Return the distance to the given node in the given tree.
38+
--
39+
-- Returns 'Nothing' if the given node is not reachable.
40+
getDistance :: Node -> LRTree a -> Maybe a
41+
getDistance v t = case findP v t of
42+
[] -> Nothing
43+
(_,d):_ -> Just d
3744

3845
getLPathNodes :: Node -> LRTree a -> Path
3946
getLPathNodes v = (\(LP p)->map fst p) . getLPath v

Data/Graph/Inductive/Query/SP.hs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ expand :: (Real b) => b -> LPath b -> Context a b -> [H.Heap b (LPath b)]
1919
expand d (LP p) (_,_,_,s) = map (\(l,v)->H.unit (l+d) (LP ((v,l+d):p))) s
2020

2121
-- | Dijkstra's shortest path algorithm.
22+
--
23+
-- The edge labels of type @b@ are the edge weights; negative edge
24+
-- weights are not supported.
2225
dijkstra :: (Graph gr, Real b)
2326
=> H.Heap b (LPath b) -- ^ Initial heap of known paths and their lengths.
2427
-> gr a b
@@ -35,24 +38,41 @@ dijkstra h g =
3538
--
3639
-- Corresponds to 'dijkstra' applied to a heap in which the only known node is
3740
-- the starting node, with a path of length 0 leading to it.
41+
--
42+
-- The edge labels of type @b@ are the edge weights; negative edge
43+
-- weights are not supported.
3844
spTree :: (Graph gr, Real b)
3945
=> Node
4046
-> gr a b
4147
-> LRTree b
4248
spTree v = dijkstra (H.unit 0 (LP [(v,0)]))
4349

44-
-- | Length of the shortest path between two nodes.
50+
-- | Length of the shortest path between two nodes, if any.
51+
--
52+
-- Returns 'Nothing' if there is no path, and @'Just' <path length>@
53+
-- otherwise.
54+
--
55+
-- The edge labels of type @b@ are the edge weights; negative edge
56+
-- weights are not supported.
4557
spLength :: (Graph gr, Real b)
4658
=> Node -- ^ Start
4759
-> Node -- ^ Destination
4860
-> gr a b
49-
-> b
61+
-> Maybe b
5062
spLength s t = getDistance t . spTree s
5163

52-
-- | Shortest path between two nodes.
64+
-- | Shortest path between two nodes, if any.
65+
--
66+
-- Returns 'Nothing' if the destination is not reachable from teh
67+
-- start node, and @'Just' <path>@ otherwise.
68+
--
69+
-- The edge labels of type @b@ are the edge weights; negative edge
70+
-- weights are not supported.
5371
sp :: (Graph gr, Real b)
5472
=> Node -- ^ Start
5573
-> Node -- ^ Destination
5674
-> gr a b
57-
-> Path
58-
sp s t = getLPathNodes t . spTree s
75+
-> Maybe Path
76+
sp s t g = case getLPathNodes t (spTree s g) of
77+
[] -> Nothing
78+
p -> Just p

test/Data/Graph/Inductive/Query/Properties.hs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Test.QuickCheck
2727

2828
import Control.Arrow (second)
2929
import Data.List (delete, sort, unfoldr, group, (\\))
30+
import Data.Maybe (fromJust, isJust, isNothing)
3031
import qualified Data.Set as S
3132

3233
#if __GLASGOW_HASKELL__ < 710
@@ -310,19 +311,48 @@ test_sp :: (ArbGraph gr) => Proxy (gr a b) -> UConnected gr () (Positive Int) ->
310311
test_sp _ cg = all test_p (map unLPath (msTree g))
311312
where
312313
-- Use Positive to avoid problems with distances containing
313-
-- negative lengths.
314+
-- negative lengths. The shortest path algorithm is Dijkstra's,
315+
-- which doesn't support negative weights.
314316
g = emap getPositive (connGraph cg)
315317

316318
gCon = emap (const 1) g `asTypeOf` g
317319

318-
test_p p = length p >= len_gCon -- Length-based test
320+
-- Length-based test
321+
test_p p = length p >= len_gCon
319322
&& length (esp v w gCon) == len_gCon
320-
&& sum (map snd p) >= spLength v w g -- Weighting-based test
323+
-- Weighting-based test
324+
&& sum (map snd p) >= fromJust (spLength v w g)
321325
where
322326
v = fst (head p)
323327
w = fst (last p)
324328

325-
len_gCon = length (sp v w gCon)
329+
len_gCon = length (fromJust $ sp v w gCon)
330+
331+
-- | Test that 'spLength' and 'sp' return a length and an connecting
332+
-- path when destination is reachable from source.
333+
test_sp_Just :: (ArbGraph gr, Graph gr, Real b) =>
334+
Proxy (gr a b) -> gr a b -> Property
335+
test_sp_Just _ g =
336+
(noNodes g >= 2 && v `elem` bfs u g) ==>
337+
isJust (spLength u v g) &&
338+
isJust maybePath &&
339+
not (null path) &&
340+
head path == u &&
341+
last path == v
342+
where
343+
[u,v] = take 2 (nodes g)
344+
maybePath@(Just path) = sp u v g
345+
346+
-- | Test that 'spLength' and 'sp' return 'Nothing' when destination
347+
-- is not reachable from source.
348+
test_sp_Nothing :: (ArbGraph gr, Graph gr, Real b) =>
349+
Proxy (gr a b) -> gr a b -> Property
350+
test_sp_Nothing _ g =
351+
(noNodes g >= 2 && not (v `elem` bfs u g)) ==>
352+
isNothing (spLength u v g) &&
353+
isNothing (sp u v g)
354+
where
355+
[u,v] = take 2 (nodes g)
326356

327357
-- -----------------------------------------------------------------------------
328358
-- TransClos

test/TestSuite.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ queryTests = describe "Queries" $ do
116116
test_maxFlow2
117117
test_maxFlow
118118
propP "msTree" test_msTree
119-
propP "sp" test_sp
119+
describe "SP" $ do
120+
propP "sp" test_sp
121+
propP "sp_Just" test_sp_Just
122+
propP "sp_Nothing" test_sp_Nothing
120123
keepSmall $ do
121124
-- Just producing the sample graph to compare against is O(|V|^2)
122125
propP "trc" test_trc

0 commit comments

Comments
 (0)