packages feed

algraph-0.7.0.0: src/Data/Graph/AdjacencyList/DFS.hs

{-|
Module      : Data.Graph.AdjacencyList.DFS
Description : Depth-first search with topological sort and longest path
Copyright   : Thodoris Papakonstantinou, 2017-2026
License     : LGPL-3
Maintainer  : dev@tpapak.com
Stability   : experimental
Portability : POSIX

Depth-first search (DFS) on directed graphs.  Produces a topological ordering,
a visited-order list, and the set of discovered vertices.  Also provides
'longestPath' on DAGs and connectivity queries.
 -}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE BangPatterns #-}


module Data.Graph.AdjacencyList.DFS
  ( DFS (..)
  , dfs
  -- * Types
  , DAG
  , Distances
  -- * get longest path from a vertex to another
  , longestPath
  , postordering
  , areConnected
  , distances
  ) where

import Data.List
import Data.Maybe
import qualified Data.IntMap   as IM
import qualified Data.IntSet   as Set
import qualified Data.Sequence as Seq

import Data.Graph.AdjacencyList

-- | Result of a depth-first search from a single source vertex.
data DFS = DFS { topsort :: [Vertex]
                 -- ^ Vertices in reverse post-order (topological sort for DAGs).
               , visited :: [Vertex]
                 -- ^ Vertices in DFS visit order.
               , discovered   :: Set.IntSet
                 -- ^ Set of all discovered vertices.
               , called :: Int
                 -- ^ Number of DFS calls made.
               } deriving (Eq, Show)

initialDFS :: DFS
initialDFS = DFS { topsort = []
                 , discovered   = Set.empty
                 , visited = []
                 , called = 0
                 }

-- | Depth first search
dfs :: Graph -> Vertex -> DFS
dfs g s = 
  let vset = Set.fromList (vertices g)
  in if not $ Set.member s vset
     then initialDFS
     else
       let depthFirstSearch :: Vertex -> DFS -> DFS
           depthFirstSearch v ac
              | Set.member v (discovered ac) = ac
              | otherwise =
              let -- Mark v as discovered BEFORE recursing (prevents revisits in cyclic graphs)
                  ac0 = ac { discovered = Set.insert v (discovered ac) }
                  ns = neighbors g v
                  !ac' = foldl' (\ac'' n -> if not (Set.member n (discovered ac''))
                                              then depthFirstSearch n ac''
                                              else ac''
                                ) ac0 ns
                  res = ac' { topsort = v : topsort ac'
                            -- Prepend to visited (reversed at end)
                            , visited = v : visited ac'
                            , called = called ac' + 1
                            }
               in res
           result = depthFirstSearch s initialDFS
        in result { visited = reverse (visited result) }

-- | Post-order traversal (reverse of 'topsort').
postordering :: DFS -> [Vertex]
postordering = reverse . topsort

-- | :)
type DAG = Graph

-- | Ginen a DAG and a vertex you get the distances
distances' :: DAG  -> Vertex -> IM.IntMap Vertex
distances' g s =
  let topsorted = topsort $ dfs g s
      initdists = foldl' (\ac v -> IM.insert v 0 ac) IM.empty $ vertices g
   in foldl' (\ac v -> 
        let neis = neighbors g v
            distv = case IM.lookup v ac of
                      Nothing -> 0
                      Just d -> d
         in foldl' (\dists' nei -> 
           let neidist = case IM.lookup nei dists' of
                           Nothing -> 0
                           Just nd -> nd
               newdist = max neidist (distv+1)
            in IM.insert nei newdist dists'
                  ) ac neis
      ) initdists topsorted

-- | Map from vertex to its distance (number of edges) from the source in a 'DAG'.
type Distances = IM.IntMap Vertex

-- | Ginen a DAG and a vertex you get the distances
distances :: DAG  -> DFS -> Vertex -> Distances
distances g dfs' s =
  let topsorted = topsort $ dfs'
      !initdists = foldl' (\ac v -> IM.insert v 0 ac) IM.empty $ vertices g
   in foldl' (\ac v -> 
        let neis = neighbors g v
            distv = case IM.lookup v ac of
                      Nothing -> 0
                      Just d -> d
         in foldl' (\dists' nei -> 
           let neidist = case IM.lookup nei dists' of
                           Nothing -> 0
                           Just nd -> nd
               newdist = max neidist (distv+1)
            in IM.insert nei newdist dists'
                  ) ac neis
      ) initdists topsorted

type TopologicalSorting = [Vertex]
-- |checks if s is predecessor of t
dependsOn :: TopologicalSorting -> Vertex -> Vertex -> Bool
dependsOn topsorted t s = elem t (snd (span ((==) s) topsorted))

-- | Check whether vertex @v@ is reachable from vertex @u@ according to the
-- given distance map (distance > 0 means reachable; @u@ is reachable from itself).
areConnected :: Distances -> Vertex -> Vertex -> Bool
areConnected dists u v = (fromJust $ IM.lookup v dists) > 0 || v == u

-- |Longest path from tail to nose
longestPath :: Graph -> Vertex -> Vertex -> [Edge]
longestPath g s t =
  let dfs' = dfs g s
      topsorted = topsort dfs'
      dists = distances g dfs' s
      revg = reverseGraph g
      disconnected = filter (\n -> not (areConnected dists s n)) $ vertices g
   in if not $ dependsOn topsorted t s
     then []
     else 
       if not $ null disconnected
          then
            let cleangraph = filterVertices (\v -> not $ elem v disconnected) g
             in longestPath cleangraph s t
          else
            let path' :: Vertex -> [Edge] -> [Edge]
                path' v p 
                  | v == s = p
                  | otherwise = 
                         let parents = neighbors revg v
                          in if null parents
                                then []
                                else
                                  if parents == [s]
                                   then (Edge s v):p
                                   else 
                                     let pred :: Vertex
                                         pred = fst $ foldl'
                                           (\(prevmax,maxdist) parent ->
                                             let currentDist =
                                                   case IM.lookup parent dists of
                                                     Nothing -> (0,0)
                                                     Just d -> (parent,d)
                                              in if maxdist < snd currentDist
                                                    then currentDist
                                                    else (prevmax,maxdist)
                                                    ) (0,0) parents
                                      in  path' pred $ (Edge pred v): p
             in path' t []