{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -fglasgow-exts #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Permutation -- Copyright : Copyright (c) , Patrick Perry -- License : BSD3 -- Maintainer : Patrick Perry -- Stability : experimental -- module Data.Permutation ( -- * The Permutation type Permutation, -- * Creating permutations permutation, identity, inverse, -- * Permutation properties size, apply, -- * Applying permutations applyWith, invertWith, -- * Converstion to/from other types -- ** @ForeignPtr@s fromForeignPtr, toForeignPtr, -- ** Lists toList, fromList, -- * Unsafe operations withPermutationPtr, unsafePermutation, unsafeApply, ) where import Control.Monad ( foldM, liftM ) import Data.IntSet ( IntSet ) import qualified Data.IntSet as IntSet import Foreign ( Ptr, ForeignPtr, mallocForeignPtrArray, withForeignPtr, pokeArray, peekArray, advancePtr, peek, peekElemOff, pokeElemOff ) import System.IO.Unsafe ( unsafePerformIO ) #if defined(__GLASGOW_HASKELL__) import GHC.Base ( realWorld# ) import GHC.IOBase ( IO(IO) ) #endif inlinePerformIO :: IO a -> a #if defined(__GLASGOW_HASKELL__) inlinePerformIO (IO m) = case m realWorld# of (# _, r #) -> r #else inlinePerformIO = unsafePerformIO #endif {-# INLINE inlinePerformIO #-} -- | Represents a permutation of the integers @[0..n)@. data Permutation = Perm {-# UNPACK #-} !Int {-# UNPACK #-} !(ForeignPtr Int) {-# UNPACK #-} !Int -- | Get the size, raw data array and offset toForeignPtr :: Permutation -> (Int, ForeignPtr Int, Int) toForeignPtr (Perm n f o) = (n, f, o) -- | Convert size and raw array to a permutation. No validation is -- performed on the arguments. fromForeignPtr :: Int -> ForeignPtr Int -> Int -> Permutation fromForeignPtr = Perm -- | Get the size of the permutation. size :: Permutation -> Int size (Perm n _ _) = n {-# INLINE size #-} -- | Perform an operation, given a pointer to the start of the -- permutation data withPermutationPtr :: Permutation -> (Ptr Int -> IO a) -> IO a withPermutationPtr (Perm _ fptr off) f = withForeignPtr fptr $ \ptr -> f (ptr `advancePtr` off) -- | Apply a permutation to an integer. The integer must be in the range -- @[0..n)@. apply :: Permutation -> Int -> Int apply p@(Perm n _ _) i | i < 0 || i >= n = error $ "applyPerm: Tried to apply permutation of size `" ++ show n ++ "' to the value `" ++ show i ++ "'." | otherwise = unsafeApply p i {-# INLINE apply #-} -- | Same as 'apply' but does not range-check the argument. unsafeApply :: Permutation -> Int -> Int unsafeApply p i = inlinePerformIO $ do withPermutationPtr p $ flip peekElemOff i {-# INLINE unsafeApply #-} -- | Create a permutation from a list of values. The list must be of length -- @n@ and contain each integer in @{0, 1, ..., (n-1) }@ exactly once. -- The permutation that is returned will send the integer @i@ to its index -- in the list. permutation :: Int -> [Int] -> Permutation permutation n is = let p = unsafePermutation n is in case isValid p of False -> error $ "Not a valid permutation." True -> p -- | Same as 'permutation', but does not check that the inputs are valid. unsafePermutation :: Int -> [Int] -> Permutation unsafePermutation n is = unsafePerformIO $ do fptr <- mallocForeignPtrArray n withForeignPtr fptr $ \ptr -> pokeArray ptr is return $ fromForeignPtr n fptr 0 {-# NOINLINE unsafePermutation #-} fromList :: [Int] -> Permutation fromList is = permutation (length is) is toList :: Permutation -> [Int] toList p = unsafePerformIO $ withPermutationPtr p $ peekArray (size p) -- | Create an identity permutation of the given size. identity :: Int -> Permutation identity n = unsafePerformIO $ do fptr <- mallocForeignPtrArray n withForeignPtr fptr $ \ptr -> pokeArray ptr [0..(n-1)] return $ fromForeignPtr n fptr 0 -- | Get the inverse of a permutation. inverse :: Permutation -> Permutation inverse p = let n = size p in unsafePerformIO $ do fptr <- mallocForeignPtrArray n withForeignPtr fptr $ \ptr -> do pokeArray ptr [0..(n-1)] invertWith (swap ptr) p return $ fromForeignPtr n fptr 0 where swap :: Ptr Int -> Int -> Int -> IO () swap ptr i j = do x <- peekElemOff ptr i y <- peekElemOff ptr j pokeElemOff ptr i y pokeElemOff ptr j x {-# NOINLINE inverse #-} isValid :: Permutation -> Bool isValid p@(Perm n _ _) = unsafePerformIO $ withPermutationPtr p $ \ptr -> do liftM and $ mapM (\i -> peekElemOff ptr i >>= \x -> isValidI ptr x i) [0..(n-1)] where isValidI :: Ptr Int -> Int -> Int -> IO Bool isValidI ptr x i = liftM and $ sequence [ inRange x, isUnique x ptr i ] inRange :: Int -> IO Bool inRange x = return $ x >= 0 && x < n isUnique :: Int -> Ptr Int -> Int -> IO Bool isUnique x ptr' n' | n' == 0 = return True | otherwise = do x' <- peek ptr' if x' == x then return False else isUnique x (ptr' `advancePtr` 1) (n'-1) -- | @applyWith swap perm@ applies the permutation as a sequence of swaps. After -- this function is applied, @OUT[i] = IN[P[i]]@ applyWith :: (Monad m) => (Int -> Int -> m ()) -> Permutation -> m () applyWith swap p = let n = size p in foldM (flip $ doCycle swap) IntSet.empty [0..(n-1)] >> return () where doCycle :: (Monad m) => (Int -> Int -> m ()) -> Int -> IntSet -> m (IntSet) doCycle swp i visited = if i `IntSet.member` visited then return visited else let visited' = IntSet.insert i visited next = unsafeApply p i in doCycle' swp i i next visited' doCycle' :: (Monad m) => (Int -> Int -> m ()) -> Int -> Int -> Int -> IntSet -> m (IntSet) doCycle' swp start cur next visited | next == start = return visited | otherwise = let visited' = IntSet.insert next visited next' = unsafeApply p next in do swp cur next doCycle' swp start next next' visited' -- | @invertWith swap p@ applies the inverse of the permutation as a -- sequence of swaps. After this function is applied, @OUT[P[i]] = IN[i]@ invertWith :: (Monad m) => (Int -> Int -> m ()) -> Permutation -> m () invertWith swap p = let n = size p in foldM (flip $ doCycle swap) IntSet.empty [0..(n-1)] >> return () where doCycle :: Monad m => (Int -> Int -> m ()) -> Int -> IntSet -> m (IntSet) doCycle swp i visited = if i `IntSet.member` visited then return visited else let visited' = IntSet.insert i visited cur = unsafeApply p i in doCycle' swp i cur visited' doCycle' :: Monad m => (Int -> Int -> m ()) -> Int -> Int -> IntSet -> m (IntSet) doCycle' swp start cur visited | cur == start = return visited | otherwise = let visited' = IntSet.insert cur visited cur' = unsafeApply p cur in do swp start cur doCycle' swp start cur' visited' instance Show Permutation where show p = "permutation " ++ show (size p) ++ " " ++ show (toList p) instance Eq Permutation where (==) p q = (size p == size q) && (toList p == toList q)