module Unbound.PermM (
Perm(..), single, compose, apply, support, isid, join, empty, restrict, mkPerm
) where
import Data.Monoid
import Data.List
import Data.Map (Map)
import qualified Data.Map as M
import qualified Data.Set as S
import Control.Arrow ((&&&))
import Control.Monad ((>=>))
newtype Perm a = Perm (Map a a)
permValid :: Ord a => Perm a -> Bool
permValid (Perm p) = all (\(_,v) -> M.member v p) (M.assocs p)
instance Ord a => Eq (Perm a) where
(Perm p1) == (Perm p2) =
all (\x -> M.findWithDefault x x p1 == M.findWithDefault x x p2) (M.keys p1) &&
all (\x -> M.findWithDefault x x p1 == M.findWithDefault x x p2) (M.keys p2)
instance Show a => Show (Perm a) where
show (Perm p) = show p
apply :: Ord a => Perm a -> a -> a
apply (Perm p) x = M.findWithDefault x x p
single :: Ord a => a -> a -> Perm a
single x y = if x == y then Perm M.empty else
Perm (M.insert x y (M.insert y x M.empty))
empty :: Perm a
empty = Perm M.empty
compose :: Ord a => Perm a -> Perm a -> Perm a
compose (Perm b) (Perm a) =
Perm (M.fromList ([ (x,M.findWithDefault y y b) | (x,y) <- M.toList a]
++ [ (x, M.findWithDefault x x b) | x <- M.keys b, M.notMember x a]))
instance Ord a => Monoid (Perm a) where
mempty = empty
mappend = compose
isid :: Ord a => Perm a -> Bool
isid (Perm p) =
M.foldrWithKey (\ a b r -> r && a == b) True p
join :: Ord a => Perm a -> Perm a -> Maybe (Perm a)
join (Perm p1) (Perm p2) =
let overlap = M.intersectionWith (==) p1 p2 in
if M.fold (&&) True overlap then
Just (Perm (M.union p1 p2))
else Nothing
support :: Ord a => Perm a -> [a]
support (Perm p) = [ x | x <- M.keys p, M.findWithDefault x x p /= x]
restrict :: Ord a => Perm a -> [a] -> Perm a
restrict (Perm p) l = Perm (foldl' (\p' k -> M.delete k p') p l)
data PartialPerm a = PP (M.Map a a) (M.Map a a)
deriving Show
emptyPP :: PartialPerm a
emptyPP = PP M.empty M.empty
extendPP :: Ord a => a -> a -> PartialPerm a -> Maybe (PartialPerm a)
extendPP x y pp@(PP mfwd mrev)
| Just y' <- M.lookup x mfwd = if y == y' then Just pp
else Nothing
| Just x' <- M.lookup y mrev = if x == x' then Just pp
else Nothing
| otherwise = Just $ PP (M.insert x y mfwd) (M.insert y x mrev)
ppToPerm :: Ord a => PartialPerm a -> Perm a
ppToPerm (PP mfwd mrev) = Perm $ foldr (uncurry M.insert) mfwd
(map (findEnd &&& id) chainStarts)
where chainStarts = S.toList (M.keysSet mfwd `S.difference` M.keysSet mrev)
findEnd x = case M.lookup x mfwd of
Nothing -> x
Just x' -> findEnd x'
mkPerm :: Ord a => [a] -> [a] -> Maybe (Perm a)
mkPerm xs ys
| length xs /= length ys = Nothing
| otherwise =
fmap ppToPerm . ($emptyPP) . foldr (>=>) return $ zipWith extendPP xs ys