{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleContexts #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Choose.MChoose -- Copyright : Copyright (c) , Patrick Perry -- License : BSD3 -- Maintainer : Patrick Perry -- Stability : experimental -- -- An overloaded interface to mutable combinations. For combination types which -- can be used with this interface, see "Data.Choose.IO" and "Data.Choose.ST". -- module Data.Choose.MChoose ( -- * Class of mutable combination types MChoose, -- * Constructing mutable combinations newChoose, newChoose_, newListChoose, newCopyChoose, copyChoose, setFirst, -- * Accessing combination elements getElem, setElem, -- * Combination properties getPossible, getSize, getElems, setElems, isValid, -- * Combination functions getComplement, getComplElems, setNext, setPrev, -- * Converstions between mutable and immutable combinations freeze, unsafeFreeze, thaw, unsafeThaw, -- * Unsafe operations unsafeNewListChoose, unsafeGetElem, unsafeSetElem, ) where import Control.Monad import Control.Monad.ST import Data.Choose.Base import Data.Choose.IOBase --------------------------------- MChoose -------------------------------- -- | Class for representing a mutable combination. The type is parameterized -- over the type of the monad, @m@, in which the mutable combination will be -- manipulated. class (Monad m) => MChoose c m | c -> m, m -> c where -- | Get the number of possibilities, @n@ in the combination. getPossible :: c -> m Int -- | Get the number of outcomes, @k@ in the combination. getSize :: c -> m Int -- | @newChoose n k@ creates a new combination of @k@ outcomes from @n@ -- possibilites initialized to the subset @{ 0, ..., k-1 }@. newChoose :: Int -> Int -> m c -- | @newChoose n k@ allocates a new combination of @k@ outcomes from -- @n@ possibilities but does not initialize it. newChoose_ :: Int -> Int -> m c unsafeGetElem :: c -> Int -> m Int unsafeSetElem :: c -> Int -> Int -> m () -- | Get a lazy list of the combination elements. The laziness makes this -- function slightly dangerous if you are modifying the combination. getElems :: c -> m [Int] -- | Set all the values of a combination from a list of elements. setElems :: c -> [Int] -> m () unsafeFreeze :: c -> m Choose unsafeThaw :: Choose -> m c -- | Construct a combination from a list of elements. -- @newListChoose n k is@ creates a combination of @k@ outcomes from @n@ -- possibilities initialized to have the @i@th element equal to @is !! i@. -- For the combination to be valid, the elements must all be unique, they -- must be in sorted order, and they all must be in the range @0 .. n-1@. newListChoose :: (MChoose c m) => Int -> Int -> [Int] -> m c newListChoose n k is = do c <- unsafeNewListChoose n k is valid <- isValid c when (not valid) $ fail "invalid combination" return c {-# INLINE newListChoose #-} unsafeNewListChoose :: (MChoose c m) => Int -> Int -> [Int] -> m c unsafeNewListChoose n k is = do c <- newChoose_ n k setElems c is return c {-# INLINE unsafeNewListChoose #-} -- | Construct a new combination by copying another. newCopyChoose :: (MChoose c m) => c -> m c newCopyChoose c = do n <- getPossible c k <- getSize c c' <- newChoose_ n k copyChoose c' c return c' {-# INLINE newCopyChoose #-} -- | @copyChoose dst src@ copies the elements of the combination @src@ -- into the combination @dst@. The two combinations must have the same -- size. copyChoose :: (MChoose c m) => c -> c -> m () copyChoose dst src = getElems src >>= setElems dst {-# INLINE copyChoose #-} -- | Set a combination to be the first subset of its size. setFirst :: (MChoose c m) => c -> m () setFirst c = do k <- getSize c setElems c [0 .. k-1] {-# INLINE setFirst #-} -- | @getElem c i@ gets the value of the @i@th element of the combination -- @c@. The index @i@ must be in the range @0..k-1@, where @n@ is the -- size of the combination. getElem :: (MChoose c m) => c -> Int -> m Int getElem c i = do k <- getSize c when (i < 0 || i >= k) $ fail "getElem: invalid index" unsafeGetElem c i {-# INLINE getElem #-} -- | @setElem c i x@ sets the value of the @i@th element of the combination -- @c@. The index @i@ must be in the range @0..k-1@, where @k@ is the -- size of the combination. setElem :: (MChoose c m) => c -> Int -> Int -> m () setElem c i x = do k <- getSize c when (i < 0 || i >= k) $ fail "getElem: invalid index" unsafeSetElem c i x {-# INLINE setElem #-} -- | Returns whether or not the combination is valid. For it to be valid, -- the elements must all be unique, they must be in sorted order, and they -- all must be in the range @0 .. n-1@, where @n@ is the number of -- possibilies in the combination. isValid :: (MChoose c m) => c -> m Bool isValid c = do n <- getPossible c is <- getElems c return $! go n (-1) is where go _ _ [] = True go n j (i:is) = i > j && i < n && go n i is {-# INLINE isValid #-} -- | Compute the complement of a combination getComplement :: (MChoose c m) => c -> m c getComplement c = do n <- getPossible c k <- getSize c d <- newChoose_ n (n-k) setElems d =<< getComplElems c return $! d {-# INLINE getComplement #-} -- | Return a lazy list of the elements in the complement of a combination. -- If the combination is a subset of @k@ outcomes from @n@ possibilities, then -- the returned list will be sorted and of length @n-k@. -- Due to the laziness, you should be careful when using this function if you -- are also modifying the combination. getComplElems :: (MChoose c m) => c -> m [Int] getComplElems c = do n <- getPossible c is <- getElems c return $ go n is 0 where go n [] j = [j .. n-1] go n (i:is) j | j == i = go n is (j+1) | otherwise = [j .. i-1] ++ go n is (i+1) {-# INLINE getComplElems #-} -- | Advance a combination to the next in lexicogrphic order and return @True@. -- If no further combinations are available, return @False@ and leave the -- combination unmodified. Starting with @[ 0 .. k-1 ]@ and repeatedly -- calling @setNext@ will iterate through all subsets of size @k@. setNext :: (MChoose c m) => c -> m Bool setNext c = do n <- getPossible c k <- getSize c if k > 0 then do findIncrement (k-1) (n-1) >>= maybe (return False) (\(i,i') -> do unsafeSetElem c i (i'+1) setAscending k (i+1) (i'+2) return True ) else return False where findIncrement i m = do i' <- unsafeGetElem c i if i' /= m then return (Just (i,i')) else recurse where recurse = if i /= 0 then findIncrement (i-1) (m-1) else return Nothing setAscending k i x | i == k = return () | otherwise = do unsafeSetElem c i x setAscending k (i+1) (x+1) {-# INLINE setNext #-} -- | Step backwards to the previous combination in lexicographic order and -- return @True@. If there is no previous combination, return @False@ and -- leave the combination unmodified. setPrev :: (MChoose c m) => c -> m Bool setPrev c = do n <- getPossible c k <- getSize c if k > 0 then do k1' <- unsafeGetElem c (k-1) findGap (k-1) k1' >>= maybe (return False) (\(i,i') -> do unsafeSetElem c i (i'-1) setAscending k (i+1) (n-k+i+1) return True ) else return False where findGap i i' | i == 0 = if i' == 0 then return $ Nothing else return $ Just (0,i') | otherwise = let j = i-1 in do j' <- unsafeGetElem c j if i' /= j'+1 then return $ Just (i,i') else findGap j j' setAscending k i x | i == k = return () | otherwise = do unsafeSetElem c i x setAscending k (i+1) (x+1) {-# INLINE setPrev #-} -- | Convert a mutable combination to an immutable one. freeze :: (MChoose c m) => c -> m Choose freeze c = unsafeFreeze =<< newCopyChoose c {-# INLINE freeze #-} -- | Convert an immutable combination to a mutable one. thaw :: (MChoose c m) => Choose -> m c thaw c = newCopyChoose =<< unsafeThaw c {-# INLINE thaw #-} --------------------------------- Instances --------------------------------- instance MChoose (STChoose s) (ST s) where getPossible = getPossibleSTChoose {-# INLINE getPossible #-} getSize = getSizeSTChoose {-# INLINE getSize #-} newChoose = newSTChoose {-# INLINE newChoose #-} newChoose_ = newSTChoose_ {-# INLINE newChoose_ #-} unsafeGetElem = unsafeGetElemSTChoose {-# INLINE unsafeGetElem #-} unsafeSetElem = unsafeSetElemSTChoose {-# INLINE unsafeSetElem #-} getElems = getElemsSTChoose {-# INLINE getElems #-} setElems = setElemsSTChoose {-# INLINE setElems #-} unsafeFreeze = unsafeFreezeSTChoose {-# INLINE unsafeFreeze #-} unsafeThaw = unsafeThawSTChoose {-# INLINE unsafeThaw #-} instance MChoose IOChoose IO where getPossible = getPossibleIOChoose {-# INLINE getPossible #-} getSize = getSizeIOChoose {-# INLINE getSize #-} newChoose = newIOChoose {-# INLINE newChoose #-} newChoose_ = newIOChoose_ {-# INLINE newChoose_ #-} unsafeGetElem = unsafeGetElemIOChoose {-# INLINE unsafeGetElem #-} unsafeSetElem = unsafeSetElemIOChoose {-# INLINE unsafeSetElem #-} getElems = getElemsIOChoose {-# INLINE getElems #-} setElems = setElemsIOChoose {-# INLINE setElems #-} unsafeFreeze = unsafeFreezeIOChoose {-# INLINE unsafeFreeze #-} unsafeThaw = unsafeThawIOChoose {-# INLINE unsafeThaw #-}