I think my error-handling here is atrocious, but I'm really new to Haskell so hoping one of you sages can help me out. This program is a command-line tool which you use by typing one of two commands: "Add " or "Check ". When you Add
a pair of ints, it makes them connected, and when you Check
two ints, it tells you whether they are connected (directly or indirectly). This uses the Union find data structure.
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Text.Read as Read
data DisjointSet k = DisjointSet
{ parent :: Map k k
, rank :: Map k Int
}
empty :: (Ord k) => DisjointSet k
empty = DisjointSet {parent = Map.empty, rank = Map.empty}
make :: Ord k => k -> DisjointSet k -> DisjointSet k
make k d
| (Map.member k (parent d)) = d
| otherwise =
d {parent = Map.insert k k (parent d), rank = Map.insert k 0 (rank d)}
find :: Ord k => k -> DisjointSet k -> Maybe (k, DisjointSet k)
find k d = do
k' <- (Map.lookup k (parent d))
if (k' == k)
then return (k, d)
else do
(k'', d') <- find k' d
return (k'', d' {parent = Map.insert k k'' (parent d')})
merge :: Ord k => k -> k -> DisjointSet k -> Maybe (DisjointSet k)
merge k1 k2 d =
let d' = (make k1 (make k2 d))
in do (k1', d'') <- find k1 d'
(k2', d''') <- find k2 d''
r1 <- Map.lookup k1' (rank d''')
r2 <- Map.lookup k2' (rank d''')
case compare r1 r2 of
LT -> return d''' {parent = Map.insert k1' k2' (parent d''')}
GT -> return d''' {parent = Map.insert k2' k1' (parent d''')}
EQ ->
return
d'''
{ parent = Map.insert k1' k2' (parent d''')
, rank = Map.insert k2' (r2 + 1) (rank d''')
}
data Action
= Add Int
Int
| Check Int
Int
deriving (Show, Read)
main :: IO ()
main = repl empty
repl :: DisjointSet Int -> IO ()
repl d = do
line <- getLine
case (Read.readMaybe line) of
Just action ->
case action of
Add k1 k2 ->
case (merge k1 k2 d) of
Just d' -> repl d'
Check k1 k2 ->
case (find k1 d) of
Just (k1', d') ->
case (find k2 d') of
Just (k2', d'') -> do
if (k1' == k2')
then putStrLn "Together"
else putStrLn "Apart"
repl d''
How do I clean up my code?