diff --git a/03_dijkstra/LeastReachableCity.hs b/03_dijkstra/LeastReachableCity.hs new file mode 100644 index 0000000..3c6abaa --- /dev/null +++ b/03_dijkstra/LeastReachableCity.hs @@ -0,0 +1,75 @@ +module LeastReachableCity(dijkstra, leastReachableCity) where + +import Types +import qualified Data.Map as Map +import qualified Data.Set as Set + +-- Well, it's just a magic number, because it's too much hassle to implement type like "Len val | Infinity" +-- TODO: get graph radius and use this number as infinity +infinity :: Len +infinity = 999999 :: Len + +leastReachableCity :: Graph -> Len -> (Point, Int) +leastReachableCity graph mileage = foldl (\(p, n) (p', n') -> + if n' < n then (p',n') + else (p, n)) + (-1, infinity) [(city, countReachableCities $ dijkstra city graph) | city <- Set.toList $ fst $ countVertices graph] where + countReachableCities :: Distances -> Int + countReachableCities distances = Map.foldl (\n val -> if (val <= mileage) && (val /= 0) then n + 1 else n) 0 distances + +dijkstra :: Point -> Graph -> Distances +dijkstra start graph = dijkstra' start graph visited_init to_visit_init distances_init where + -- Helper (so we don' need to call dijkstra initializing empty visited, to_visit etc) + dijkstra' :: Point -> Graph -> Vertices -> Vertices -> Distances -> Distances + dijkstra' start graph visited to_visit distances = if Set.null to_visit + then + distances + else + dijkstra' + (findMinNotVisited to_visit distances) + graph + (Set.insert start visited) + (Set.union (Set.delete start to_visit) $ findNotVisitedNeighbours graph visited start) + (updateDistances graph start distances) + -- Other + to_visit_init = (Set.insert start $ Set.empty :: Vertices) + distances_init = (infinityDistances start $ snd $ countVertices graph) + visited_init = (Set.empty :: Vertices) + +updateDistances :: Graph -> Point -> Distances -> Distances +updateDistances graph point distances = snd $ Map.foldrWithKey decideMin ((point, Map.findWithDefault (-1) point distances), Map.empty :: Distances) distances where + decideMin :: Point -> Len -> ((Point, Len), Distances) -> ((Point, Len), Distances) + decideMin p' l' ((p, l), dist) = if findDistance graph p p' + l < l' then + ((p, l), Map.insert p' (l + findDistance graph p p') dist) else + ((p, l), Map.insert p' l' dist) + +findDistance :: Graph -> Point -> Point -> Len +findDistance ((Node (a, b, len)) : graph) p p' = if ((a == p) && (b == p') || (b == p) && (a == p')) then len else findDistance graph p p' +findDistance _ _ _ = infinity + +findMinNotVisited :: Vertices -> Distances -> Point +findMinNotVisited to_visit distances = fst $ Set.foldl (\(p, l) p' -> case Map.lookup p' distances of + (Just l') -> if l' < l then + (p', l') + else (p,l)) + (-1, infinity) to_visit + +infinityDistances :: Point -> Int -> Distances +infinityDistances point n = Map.fromList $ (point, 0) : [(x, infinity) | x <- [1..n], x /= point] + +countVertices :: Graph -> (Vertices, Int) +countVertices graph = foldl f (Set.empty :: Vertices, 0) graph where + f :: (Vertices, Int) -> Node -> (Vertices, Int) + f (vertices, n) (Node (a, b, _)) = + let aIsNotMember = not $ Set.member a vertices + bIsNotMember = not $ Set.member b vertices in if (aIsNotMember && bIsNotMember) then (Set.insert a $ Set.insert b vertices, n + 2) + else if (aIsNotMember) then (Set.insert a vertices, n + 1) + else if (bIsNotMember) then (Set.insert b vertices, n + 1) + else (vertices, n) + +findNotVisitedNeighbours :: Graph -> Vertices -> Point -> Vertices +findNotVisitedNeighbours graph visited point = foldl f (Set.empty :: Vertices) graph where + f :: Vertices -> Node -> Vertices + f vertices (Node (a, b, _)) = if ((a == point) && (not $ Set.member b visited)) then Set.insert b vertices + else if ((b == point) && (not $ Set.member a visited)) then Set.insert a vertices + else vertices diff --git a/03_dijkstra/Types.hs b/03_dijkstra/Types.hs new file mode 100644 index 0000000..533feda --- /dev/null +++ b/03_dijkstra/Types.hs @@ -0,0 +1,13 @@ +module Types where + +import qualified Data.Map as Map +import qualified Data.Set as Set + +type Point = Int +type Len = Int + +data Node = Node (Point, Point, Len) deriving Show +type Graph = [Node] + +type Distances = Map.Map Point Len +type Vertices = Set.Set Point diff --git a/03_dijkstra/data b/03_dijkstra/data new file mode 100644 index 0000000..9903946 --- /dev/null +++ b/03_dijkstra/data @@ -0,0 +1,9 @@ +4 + +1 2 10 +1 4 2 +1 3 3 +3 2 5 +2 4 6 +4 5 4 +5 3 1 diff --git a/03_dijkstra/main.hs b/03_dijkstra/main.hs new file mode 100644 index 0000000..8936dc7 --- /dev/null +++ b/03_dijkstra/main.hs @@ -0,0 +1,46 @@ +module Main where + +import System.IO +import System.Environment + +import qualified Data.Map as Map +import qualified Data.Set as Set + +import LeastReachableCity +import Types + +{- format of input file is: + - mileage + - from to len + - from to len + - ... + - where "from" and "to" are numbers of graph vertices, "len" is distance between them + - -} +main :: IO() +main = do + argv <- getArgs + + filename <- if not $ null argv then do + head <$> getArgs + else do + putStrLn "Usage: ./lrc [filename]\nNow defaulting to file \"data\"" + return "data" + + content <- readFile filename + let mileage = read $ head $ lines content :: Len + let graph = readGraph $ tail content + let (city, n) = leastReachableCity graph mileage + putStrLn $ "The least reachable city is " ++ (show city) ++ " (" ++ (show n) ++ " city/ies is/are reachable)" + +{- For parsing graph using content of the file -} +parseStrNode :: [String] -> Node +parseStrNode [a, b, c] = Node (p a, p b, l c) where + p x = read x :: Point + l x = read x :: Len + +readGraph :: String -> Graph +readGraph content = parse lst where + parse (x:xs) = if length ws == 3 then (parseStrNode ws) : (parse xs) else parse xs where + ws = words x + parse _ = [] + lst = lines content