-- | Non-crossing partitions. -- -- See eg. -- -- Non-crossing partitions of the set @[1..n]@ are encoded as lists of lists -- in standard form: Entries decreasing in each block and blocks listed in increasing order of their first entries. -- For example the partition in the diagram -- -- <> -- -- is represented as -- -- > NonCrossing [[3],[5,4,2],[7,6,1],[9,8]] -- {-# LANGUAGE BangPatterns #-} module Math.Combinat.Partitions.NonCrossing where -------------------------------------------------------------------------------- import Control.Applicative import Data.List import Data.Ord import System.Random import Math.Combinat.Numbers import Math.Combinat.LatticePaths import Math.Combinat.Helper import Math.Combinat.Partitions.Set import Math.Combinat.Classes -------------------------------------------------------------------------------- -- * The type of non-crossing partitions -- | A non-crossing partition of the set @[1..n]@ in standard form: -- entries decreasing in each block and blocks listed in increasing order of their first entries. newtype NonCrossing = NonCrossing [[Int]] deriving (Eq,Ord,Show,Read) -- | Checks whether a set partition is noncrossing. -- -- Implementation method: we convert to a Dyck path and then back again, and finally compare. -- Probably not very efficient, but should be better than a naive check for crosses...) -- _isNonCrossing :: [[Int]] -> Bool _isNonCrossing zzs0 = _isNonCrossingUnsafe (_standardizeNonCrossing zzs0) -- | Warning: This function assumes the standard ordering! _isNonCrossingUnsafe :: [[Int]] -> Bool _isNonCrossingUnsafe zzs = case _nonCrossingPartitionToDyckPathMaybe zzs of Nothing -> False Just dyck -> case dyckPathToNonCrossingPartitionMaybe dyck of Nothing -> False Just (NonCrossing yys) -> yys == zzs -- | Convert to standard form: entries decreasing in each block -- and blocks listed in increasing order of their first entries. _standardizeNonCrossing :: [[Int]] -> [[Int]] _standardizeNonCrossing = sortBy (comparing myhead) . map reverseSort where myhead xs = case xs of (x:xs) -> x [] -> error "_standardizeNonCrossing: empty subset" fromNonCrossing :: NonCrossing -> [[Int]] fromNonCrossing (NonCrossing xs) = xs toNonCrossingUnsafe :: [[Int]] -> NonCrossing toNonCrossingUnsafe = NonCrossing -- | Throws an error if the input is not a non-crossing partition toNonCrossing :: [[Int]] -> NonCrossing toNonCrossing xxs = case toNonCrossingMaybe xxs of Just nc -> nc Nothing -> error "toNonCrossing: not a non-crossing partition" toNonCrossingMaybe :: [[Int]] -> Maybe NonCrossing toNonCrossingMaybe xxs0 = if _isNonCrossingUnsafe xxs then Just $ NonCrossing xxs else Nothing where xxs = _standardizeNonCrossing xxs0 -- | If a set partition is actually non-crossing, then we can convert it setPartitionToNonCrossing :: SetPartition -> Maybe NonCrossing setPartitionToNonCrossing (SetPartition zzs0) = if _isNonCrossingUnsafe zzs then Just $ NonCrossing zzs else Nothing where zzs = _standardizeNonCrossing zzs0 instance HasNumberOfParts NonCrossing where numberOfParts (NonCrossing p) = length p -------------------------------------------------------------------------------- -- * Bijection to Dyck paths -- | Bijection between Dyck paths and noncrossing partitions -- -- Based on: David Callan: /Sets, Lists and Noncrossing Partitions/ -- -- Fails if the input is not a Dyck path. dyckPathToNonCrossingPartition :: LatticePath -> NonCrossing dyckPathToNonCrossingPartition = NonCrossing . go 0 [] [] [] where go :: Int -> [Int] -> [Int] -> [[Int]] -> LatticePath -> [[Int]] go !cnt stack small big path = case path of (x:xs) -> case x of UpStep -> let cnt' = cnt + 1 in case xs of (y:ys) -> case y of UpStep -> go cnt' (cnt':stack) small big xs DownStep -> go cnt' (cnt':stack) [] (reverse small : big) xs [] -> error "dyckPathToNonCrossingPartition: last step is an UpStep (thus input was not a Dyck path)" DownStep -> case stack of (k:ks) -> go cnt ks (k:small) big xs [] -> error "dyckPathToNonCrossingPartition: empty stack, shouldn't happen (thus input was not a Dyck path)" [] -> tail $ reverse (reverse small : big) -- | Safe version of 'dyckPathToNonCrossingPartition' dyckPathToNonCrossingPartitionMaybe :: LatticePath -> Maybe NonCrossing dyckPathToNonCrossingPartitionMaybe = fmap NonCrossing . go 0 [] [] [] where go :: Int -> [Int] -> [Int] -> [[Int]] -> LatticePath -> Maybe [[Int]] go !cnt stack small big path = case path of (x:xs) -> case x of UpStep -> let cnt' = cnt + 1 in case xs of (y:ys) -> case y of UpStep -> go cnt' (cnt':stack) small big xs DownStep -> go cnt' (cnt':stack) [] (reverse small : big) xs [] -> Nothing DownStep -> case stack of (k:ks) -> go cnt ks (k:small) big xs [] -> Nothing [] -> Just $ tail $ reverse (reverse small : big) -- | The inverse bijection (should never fail proper 'NonCrossing'-s) nonCrossingPartitionToDyckPath :: NonCrossing -> LatticePath nonCrossingPartitionToDyckPath (NonCrossing zzs) = go 0 zzs where go !k (ys@(y:_):yys) = replicate (y-k) UpStep ++ replicate (length ys) DownStep ++ go y yys go !k [] = [] go _ _ = error "nonCrossingPartitionToDyckPath: shouldnt't happen" -- | Safe version 'nonCrossingPartitionToDyckPath' _nonCrossingPartitionToDyckPathMaybe :: [[Int]] -> Maybe LatticePath _nonCrossingPartitionToDyckPathMaybe = go 0 where go !k (ys@(y:_):yys) = fmap (\zs -> replicate (y-k) UpStep ++ replicate (length ys) DownStep ++ zs) (go y yys) go !k [] = Just [] go _ _ = Nothing -------------------------------------------------------------------------------- {- -- this should be mapped to NonCrossing [[3],[5,4,2],[7,6,1],[9,8]] testpath = [u,u,u,d,u,u,d,d,d,u,u,d,d,d,u,u,d,d] where u = UpStep d = DownStep testnc = NonCrossing [[3],[5,4,2],[7,6,1],[9,8]] -} -------------------------------------------------------------------------------- -- * Generating non-crossing partitions -- | Lists all non-crossing partitions of @[1..n]@ -- -- Equivalent to (but orders of magnitude faster than) filtering out the non-crossing ones: -- -- > (sort $ catMaybes $ map setPartitionToNonCrossing $ setPartitions n) == sort (nonCrossingPartitions n) -- nonCrossingPartitions :: Int -> [NonCrossing] nonCrossingPartitions = map dyckPathToNonCrossingPartition . dyckPaths -- | Lists all non-crossing partitions of @[1..n]@ into @k@ parts. -- -- > sort (nonCrossingPartitionsWithKParts k n) == sort [ p | p <- nonCrossingPartitions n , numberOfParts p == k ] -- nonCrossingPartitionsWithKParts :: Int -- ^ @k@ = number of parts -> Int -- ^ @n@ = size of the set -> [NonCrossing] nonCrossingPartitionsWithKParts k n = map dyckPathToNonCrossingPartition $ peakingDyckPaths k n -- | Non-crossing partitions are counted by the Catalan numbers countNonCrossingPartitions :: Int -> Integer countNonCrossingPartitions = countDyckPaths -- | Non-crossing partitions with @k@ parts are counted by the Naranaya numbers countNonCrossingPartitionsWithKParts :: Int -- ^ @k@ = number of parts -> Int -- ^ @n@ = size of the set -> Integer countNonCrossingPartitionsWithKParts = countPeakingDyckPaths -------------------------------------------------------------------------------- -- | Uniformly random non-crossing partition randomNonCrossingPartition :: RandomGen g => Int -> g -> (NonCrossing,g) randomNonCrossingPartition n g0 = (dyckPathToNonCrossingPartition dyck, g1) where (dyck,g1) = randomDyckPath n g0 --------------------------------------------------------------------------------