packages feed

alms-0.6.0: src/Data/UnionFind.hs

{- | Based on Simonet's Dalton constraint solver -}
module Data.UnionFind (
  -- * An implementation on top of 'MonadRef'
  Proxy,
  create, desc, setDesc, repr,
  sameRepr, isRepr, coalesce, coalesce_, linkto,
) where

import Util
import Util.Eq1
import Util.MonadRef

import Prelude ()

---
--- Representaiton and basic, low-level operations
---

newtype Proxy p a = Proxy { unProxy ∷ p (Either a (Proxy p a)) }

instance Eq1 p ⇒ Eq1 (Proxy p) where
  Proxy p1 `eq1` Proxy p2 = p1 `eq1` p2

-- | To create a new set with the given representative
create  ∷ MonadRef p m ⇒ a → m (Proxy p a)
create   = liftM Proxy . newRef . Left

-- | To follow a link, either to the end or to another link
follow  ∷ MonadRef p m ⇒ Proxy p a → m (Either a (Proxy p a))
follow   = readRef . unProxy

-- | To replace the contents of a link with a representative
--   or another link
replace ∷ MonadRef p m ⇒ Proxy p a → Either a (Proxy p a) → m ()
replace  = writeRef . unProxy

-- | Find the representative of a set
repr    ∷ MonadRef p m ⇒ Proxy p a → m (Proxy p a)
repr    = liftM fst . loop where
  loop proxy = do
    link ← follow proxy
    case link of
      Left _       → return (proxy, False)
      Right proxy' → do
        (proxy'', changed) ← loop proxy'
        when changed $ replace proxy (Right proxy'')
        return (proxy'', True)

-- | Find the descriptor of a set
desc     ∷ MonadRef p m ⇒ Proxy p a → m a
desc proxy = do
  link ← follow proxy
  case link of
    Left a       → return a
    Right proxy' → desc =<< repr proxy'

-- | Change the descriptor of a set
setDesc ∷ MonadRef p m ⇒ Proxy p a → a → m ()
setDesc proxy a = flip replace (Left a) =<< repr proxy

-- | Join two proxies, using the given function to combine their
--   descriptors.
coalesce ∷ MonadRef p m ⇒
           (a → a → m (a, b)) → Proxy p a → Proxy p a → m (Maybe b)
coalesce combine proxy1 proxy2 = do
  proxy1' ← repr proxy1
  proxy2' ← repr proxy2
  if (proxy1' `eq1` proxy2')
    then return Nothing
    else do
      a1      ← desc proxy1'
      a2      ← desc proxy2'
      (a', b) ← combine a1 a2
      replace proxy1' (Right proxy2')
      replace proxy2' (Left a')
      return (Just b)

coalesce_ ∷ MonadRef p m ⇒ (a → a → m a) → Proxy p a → Proxy p a → m ()
coalesce_ combine proxy1 proxy2 = do
  coalesce (liftM (,()) <$$> combine) proxy1 proxy2
  return ()

-- | Make the first proxy point to the second, keeping the second
--   proxy's descriptor
linkto ∷ MonadRef p m ⇒ Proxy p a → Proxy p a → m ()
linkto = coalesce_ (const . return)

-- | Is the given proxy object the representative of its set?
isRepr ∷ MonadRef p m ⇒ Proxy p a → m Bool
isRepr = liftM (either (const True) (const False)) . follow

-- | Are two proxy objects from the same set?
sameRepr ∷ MonadRef p m ⇒ Proxy p a → Proxy p a → m Bool
sameRepr proxy1 proxy2 = liftM2 eq1 (repr proxy1) (repr proxy2)