multiset-comb 0.1 → 0.2
raw patch · 2 files changed
+228/−60 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Math.Combinatorics.Multiset: instance (Show a) => Show (RMultiSet a)
- Math.Combinatorics.Multiset: type MultiSet a = [(a, Count)]
+ Math.Combinatorics.Multiset: (+:) :: (a, Count) -> Multiset a -> Multiset a
+ Math.Combinatorics.Multiset: MS :: [(a, Count)] -> Multiset a
+ Math.Combinatorics.Multiset: consMS :: (a, Count) -> Multiset a -> Multiset a
+ Math.Combinatorics.Multiset: cycles :: Multiset a -> [[a]]
+ Math.Combinatorics.Multiset: disjUnion :: Multiset a -> Multiset a -> Multiset a
+ Math.Combinatorics.Multiset: disjUnions :: [Multiset a] -> Multiset a
+ Math.Combinatorics.Multiset: fromCounts :: [(a, Count)] -> Multiset a
+ Math.Combinatorics.Multiset: fromDistinctList :: [a] -> Multiset a
+ Math.Combinatorics.Multiset: fromListEq :: (Eq a) => [a] -> Multiset a
+ Math.Combinatorics.Multiset: getCounts :: Multiset a -> [Count]
+ Math.Combinatorics.Multiset: instance (Show a) => Show (Multiset a)
+ Math.Combinatorics.Multiset: instance (Show a) => Show (RMultiset a)
+ Math.Combinatorics.Multiset: instance Functor Multiset
+ Math.Combinatorics.Multiset: kSubsets :: Count -> Multiset a -> [Multiset a]
+ Math.Combinatorics.Multiset: newtype Multiset a
+ Math.Combinatorics.Multiset: sequenceMS :: Multiset [a] -> [Multiset a]
+ Math.Combinatorics.Multiset: splits :: Multiset a -> [(Multiset a, Multiset a)]
+ Math.Combinatorics.Multiset: toCounts :: Multiset a -> [(a, Count)]
- Math.Combinatorics.Multiset: fromList :: (Ord a) => [a] -> MultiSet a
+ Math.Combinatorics.Multiset: fromList :: (Ord a) => [a] -> Multiset a
- Math.Combinatorics.Multiset: partitions :: MultiSet a -> [MultiSet (MultiSet a)]
+ Math.Combinatorics.Multiset: partitions :: Multiset a -> [Multiset (Multiset a)]
- Math.Combinatorics.Multiset: permutations :: MultiSet a -> [[a]]
+ Math.Combinatorics.Multiset: permutations :: Multiset a -> [[a]]
- Math.Combinatorics.Multiset: permutationsRLE :: MultiSet a -> [[(a, Count)]]
+ Math.Combinatorics.Multiset: permutationsRLE :: Multiset a -> [[(a, Count)]]
- Math.Combinatorics.Multiset: toList :: MultiSet a -> [a]
+ Math.Combinatorics.Multiset: toList :: Multiset a -> [a]
- Math.Combinatorics.Multiset: vPartitions :: Vec -> [MultiSet (Vec)]
+ Math.Combinatorics.Multiset: vPartitions :: Vec -> [Multiset Vec]
Files
- Math/Combinatorics/Multiset.hs +217/−56
- multiset-comb.cabal +11/−4
Math/Combinatorics/Multiset.hs view
@@ -1,16 +1,30 @@--- | Efficient combinatorial algorithms to generate all permutations--- and partitions of a multiset. Note that an 'Eq' or 'Ord'--- instance on the elements is /not/ required; the algorithms are--- careful to keep track of which things are (by construction) equal--- to which other things, so equality testing is not needed.++-- | Efficient combinatorial algorithms over multisets, including+-- generating all permutations, partitions, subsets, cycles, and+-- other combinatorial structures based on multisets. Note that an+-- 'Eq' or 'Ord' instance on the elements is /not/ required; the+-- algorithms are careful to keep track of which things are (by+-- construction) equal to which other things, so equality testing is+-- not needed. module Math.Combinatorics.Multiset- ( -- * The 'MultiSet' type+ ( -- * The 'Multiset' type Count- , MultiSet+ , Multiset(..)+ , consMS, (+:)++ -- ** Conversions , toList , fromList+ , fromListEq+ , fromDistinctList+ , fromCounts+ , getCounts + -- ** Operations+ , disjUnion+ , disjUnions+ -- * Permutations , permutations@@ -22,53 +36,120 @@ , vPartitions , partitions + -- * Submultisets++ , splits+ , kSubsets++ -- * Cycles++ , cycles++ -- * Miscellaneous++ , sequenceMS+ ) where import Data.List (group, sort)-import Control.Arrow (first, second, (&&&))+import Control.Arrow (first, second, (&&&), (***)) import Data.Maybe (catMaybes) type Count = Int --- | A multiset is a list of (element, count) pairs. We maintain the--- invariants that the counts are always positive, and no element--- ever appears more than once.-type MultiSet a = [(a, Count)]+-- | A multiset is represented as a list of (element, count) pairs.+-- We maintain the invariants that the counts are always positive,+-- and no element ever appears more than once.+newtype Multiset a = MS { toCounts :: [(a, Count)] }+ deriving (Show) +-- | Construct a 'Multiset' from a list of (element, count) pairs.+-- Precondition: the counts must all be positive, and there must not+-- be any duplicate elements.+fromCounts :: [(a, Count)] -> Multiset a+fromCounts = MS++-- | Extract just the element counts from a multiset, forgetting the+-- elements.+getCounts :: Multiset a -> [Count]+getCounts (MS xs) = map snd xs++liftMS :: ([(a, Count)] -> [(b, Count)]) -> Multiset a -> Multiset b+liftMS f (MS m) = MS (f m)++-- | Add an element with multiplicity to a multiset. Precondition:+-- the new element is distinct from all elements already in the+-- multiset.+consMS :: (a, Count) -> Multiset a -> Multiset a+consMS e (MS m) = MS (e:m)++-- | A convenient shorthand for 'consMS'.+(+:) :: (a, Count) -> Multiset a -> Multiset a+(+:) = consMS++instance Functor Multiset where+ fmap f = fromCounts . (map . first $ f) . toCounts+ -- | Convert a multiset to a list.-toList :: MultiSet a -> [a]-toList = concatMap (uncurry (flip replicate))+toList :: Multiset a -> [a]+toList = expandCounts . toCounts --- | Convert a list to a multiset. This method is provided just for--- convenience; you can of course construct your own 'MultiSet's--- directly (especially if the type of the elements is not an--- instance of 'Ord').-fromList :: Ord a => [a] -> MultiSet a-fromList = map (head &&& length) . group . sort+expandCounts :: [(a, Count)] -> [a]+expandCounts = concatMap (uncurry (flip replicate)) +-- | Efficiently convert a list to a multiset, given an 'Ord' instance+-- for the elements. This method is provided just for convenience.+-- you can also use 'fromListEq' with only an 'Eq' instance, or+-- construct 'Multiset's directly using 'fromCounts'.+fromList :: Ord a => [a] -> Multiset a+fromList = fromCounts . map (head &&& length) . group . sort++-- | Convert a list to a multiset, given an 'Eq' instance for the+-- elements.+fromListEq :: Eq a => [a] -> Multiset a+fromListEq = fromCounts . fromListEq'+ where fromListEq' [] = []+ fromListEq' (x:xs) = (x, 1 + count x xs) : fromListEq' (filter (/=x) xs)+ count x = length . filter (==x)++-- | Make a multiset with one copy of each element from a list of+-- distinct elements.+fromDistinctList :: [a] -> Multiset a+fromDistinctList = fromCounts . map (\x -> (x,1))++-- | Form the disjoint union of two multisets; i.e. we assume the two+-- multisets share no elements in common.+disjUnion :: Multiset a -> Multiset a -> Multiset a+disjUnion (MS xs) (MS ys) = MS (xs ++ ys)++-- | Form the disjoint union of a collection of multisets. We assume+-- that the multisets all have distinct elements.+disjUnions :: [Multiset a] -> Multiset a+disjUnions = foldr disjUnion (MS [])+ -- | In order to generate permutations of a multiset, we need to keep -- track of the most recently used element in the permutation being -- built, so that we don't use it again immediately. The--- 'RMultiSet' type (for \"restricted multiset\") records this+-- 'RMultiset' type (for \"restricted multiset\") records this -- information, consisting of a multiset possibly paired with an -- element (with multiplicity) which is also part of the multiset, -- but should not be used at the beginning of permutations.-data RMultiSet a = RMS (Maybe (a, Count)) (MultiSet a)+data RMultiset a = RMS (Maybe (a, Count)) [(a,Count)] deriving Show --- | Convert a 'MultiSet' to a 'RMultiSet' (with no avoided element).-toRMS :: MultiSet a -> RMultiSet a-toRMS = RMS Nothing+-- | Convert a 'Multiset' to a 'RMultiset' (with no avoided element).+toRMS :: Multiset a -> RMultiset a+toRMS = RMS Nothing . toCounts --- | Convert a 'RMultiSet' to a 'MultiSet'.-fromRMS :: RMultiSet a -> MultiSet a-fromRMS (RMS Nothing m) = m-fromRMS (RMS (Just e) m) = e:m+-- | Convert a 'RMultiset' to a 'Multiset'.+fromRMS :: RMultiset a -> Multiset a+fromRMS (RMS Nothing m) = MS m+fromRMS (RMS (Just e) m) = MS (e:m) -- | List all the distinct permutations of the elements of a -- multiset. ----- For example, @permutations [('a',1), ('b',2)] ==+-- For example, @permutations (fromList \"abb\") == -- [\"abb\",\"bba\",\"bab\"]@, whereas @Data.List.permutations -- \"abb\" == [\"abb\",\"bab\",\"bba\",\"bba\",\"bab\",\"abb\"]@. -- This function is equivalent to, but /much/ more efficient than,@@ -77,8 +158,8 @@ -- -- Note that this is a specialized version of 'permutationsRLE', -- where each run has been expanded via 'replicate'.-permutations :: MultiSet a -> [[a]]-permutations = map toList . permutationsRLE+permutations :: Multiset a -> [[a]]+permutations = map expandCounts . permutationsRLE -- | List all the distinct permutations of the elements of a multiset, -- with each permutation run-length encoded. (Note that the@@ -88,19 +169,19 @@ -- For example, @permutationsRLE [('a',1), ('b',2)] == -- [[('a',1),('b',2)],[('b',2),('a',1)],[('b',1),('a',1),('b',1)]]@. ----- (Note that although the output type is equivalent to @[MultiSet--- a]@, we don't call it that since the output may violate the--- 'MultiSet' invariant that no element should appear more than--- once. And indeed, morally this function does not output--- multisets at all.)-permutationsRLE :: MultiSet a -> [[(a,Count)]]-permutationsRLE [] = [[]]-permutationsRLE m = permutationsRLE' (toRMS m)+-- (Note that although the output type is newtype-equivalent to+-- @[Multiset a]@, we don't call it that since the output may+-- violate the 'Multiset' invariant that no element should appear+-- more than once. And indeed, morally this function does not+-- output multisets at all.)+permutationsRLE :: Multiset a -> [[(a,Count)]]+permutationsRLE (MS []) = [[]]+permutationsRLE m = permutationsRLE' (toRMS m) -- | List all the (run-length encoded) distinct permutations of the--- elements of a multiset which do not start with the element to avoid--- (if any).-permutationsRLE' :: RMultiSet a -> [[(a,Count)]]+-- elements of a multiset which do not start with the element to+-- avoid (if any).+permutationsRLE' :: RMultiset a -> [[(a,Count)]] -- If only one element is left, there's only one permutation. permutationsRLE' (RMS Nothing [(x,n)]) = [[(x,n)]]@@ -116,7 +197,7 @@ -- | Select an element + multiplicity from a multiset in all possible -- ways, appropriately keeping track of elements to avoid at the -- start of permutations.-selectRMS :: RMultiSet a -> [((a, Count), RMultiSet a)]+selectRMS :: RMultiset a -> [((a, Count), RMultiset a)] -- No elements to select. selectRMS (RMS _ []) = []@@ -138,7 +219,7 @@ -- Finally, we can recursively choose something other than x. map (second (consRMS (x,n))) (selectRMS (RMS e ms)) -consRMS :: (a, Count) -> RMultiSet a -> RMultiSet a+consRMS :: (a, Count) -> RMultiset a -> RMultiset a consRMS x (RMS e m) = RMS e (x:m) @@ -150,11 +231,11 @@ -- instance Arbitrary Count where -- arbitrary = elements (map ArbCount [1..3]) --- prop_perms_distinct :: MultiSet Char ArbCount -> Bool+-- prop_perms_distinct :: Multiset Char ArbCount -> Bool -- prop_perms_distinct m = length ps == length (nub ps) -- where ps = permutations m --- prop_perms_are_perms :: MultiSet Char ArbCount -> Bool+-- prop_perms_are_perms :: Multiset Char ArbCount -> Bool -- prop_perms_are_perms m = all ((==l') . sort) (permutations m) -- where l' = sort (toList m) @@ -213,14 +294,15 @@ -- <http://www.haskell.org/sitewiki/images/d/dd/TMR-Issue8.pdf> -- -- See that article for a detailed discussion of the code and how it works.-vPartitions :: Vec -> [MultiSet (Vec)]+vPartitions :: Vec -> [Multiset Vec] vPartitions v = vPart v (vZero v) where- vPart v _ | vIsZero v = [[]]+ vPart v _ | vIsZero v = [MS []] vPart v vL | v <= vL = []- | otherwise = [(v,1)] : [ (v',k) : p' | v' <- withinFromTo v (vHalf v) (vInc v vL)- , k <- [1 .. (v `vDiv` v')]- , p' <- vPart (v .-. (k *. v')) v' ]+ | otherwise = MS [(v,1)]+ : [ (v',k) +: p' | v' <- withinFromTo v (vHalf v) (vInc v vL)+ , k <- [1 .. (v `vDiv` v')]+ , p' <- vPart (v .-. (k *. v')) v' ] -- | 'vHalf v' computes the \"lexicographic half\" of 'v', that is, -- the vector which is the middle element (biased towards the end)@@ -264,8 +346,87 @@ -- each partition is represented as a multiset of parts (each of -- which is a multiset) in order to properly reflect the fact that -- some parts may occur multiple times.-partitions :: MultiSet a -> [MultiSet (MultiSet a)]-partitions [] = [[]]-partitions m = (map . map . first) (combine elts) $ vPartitions counts+partitions :: Multiset a -> [Multiset (Multiset a)]+partitions (MS []) = [MS []]+partitions (MS m) = (map . fmap) (combine elts) $ vPartitions counts where (elts, counts) = unzip m- combine es cs = filter ((/=0) . snd) $ zip es cs+ combine es cs = MS . filter ((/=0) . snd) $ zip es cs++-- | Generate all splittings of a multiset into two submultisets,+-- i.e. all size-two partitions.+splits :: Multiset a -> [(Multiset a, Multiset a)]+splits (MS []) = [(MS [], MS [])]+splits (MS ((x,n):m)) =+ for [0..n] $ \k ->+ map (addElt x k *** addElt x (n-k)) (splits (MS m))++-- | Generate all size-k submultisets.+kSubsets :: Count -> Multiset a -> [Multiset a]+kSubsets 0 _ = [MS []]+kSubsets _ (MS []) = []+kSubsets k (MS ((x,n):m)) =+ for [0 .. min k n] $ \j ->+ map (addElt x j) (kSubsets (k - j) (MS m))++for = flip concatMap++addElt _ 0 = id+addElt x k = ((x,k) +:)++-- | Generate all distinct cycles, aka necklaces, with elements taken+-- from a multiset. See J. Sawada, \"A fast algorithm to generate+-- necklaces with fixed content\", J. Theor. Comput. Sci. 301 (2003)+-- pp. 477-489.+--+-- Given the ordering on the elements of the multiset based on their+-- position in the multiset representation (with \"smaller\"+-- elements first), in @map reverse (cycles m)@, each generated+-- cycle is lexicographically smallest among all its cyclic shifts,+-- and furthermore, the cycles occur in reverse lexicographic+-- order. (It's simply more convenient/efficient to generate the+-- cycles reversed in this way, and of course we get the same set of+-- cycles either way.)+--+-- For example, @cycles (fromList \"aabbc\") ==+-- [\"cabba\",\"bcaba\",\"cbaba\",\"bbcaa\",\"bcbaa\",\"cbbaa\"]@.+cycles :: Multiset a -> [[a]]+cycles (MS []) = [] -- no such thing as an empty cycle+cycles m@(MS ((x1,n1):xs))+ | n1 == 1 = (cycles' n 2 1 [(0,x1)] (reverse $ zip [1..] xs))+ | otherwise = (cycles' n 2 1 [(0,x1)] (reverse $ zip [0..] ((x1,n1-1):xs)))+ where n = sum . getCounts $ m++-- | The first parameter is the length of the necklaces being+-- generated. The second parameter @p@ is the length of the longest+-- prefix of @pre@ which is a Lyndon word, i.e. an aperiodic+-- necklace. @pre@ is the current (reversed) prefix of the+-- necklaces being generated.+cycles' :: Int -> Int -> Int -> [(Int, a)] -> [(Int, (a,Count))] -> [[a]]+cycles' n t p pre [] | n `mod` p == 0 = [map snd pre]+ | otherwise = []++cycles' n t p pre xs =+ (takeWhile ((>=atp) . fst) xs) >>= \(j, (xj,nj)) ->+ cycles' n (t+1) (if j == atp then p else t)+ ((j,xj):pre)+ (remove j xs)+ where atp = fst $ pre !! (p - 1)++remove j [] = []+remove j (x@(j',(xj,nj)):xs)+ | j == j' && nj == 1 = xs+ | j == j' = (j',(xj,nj-1)):xs+ | otherwise = x:remove j xs++-- | Take a multiset of lists, and select one element from each list+-- in every possible combination to form a list of multisets. We+-- assume that all the list elements are distinct.+sequenceMS :: Multiset [a] -> [Multiset a]+sequenceMS = map disjUnions+ . sequence+ . map (\(xs, n) -> kSubsets n (MS $ uncollate (xs, n)))+ . toCounts++uncollate :: ([a], Count) -> [(a, Count)]+uncollate (xs, n) = map (\x -> (x,n)) xs+
multiset-comb.cabal view
@@ -1,7 +1,11 @@ Name: multiset-comb-Version: 0.1-Synopsis: Combinatorial operations on multisets-Description: Efficiently generate all permutations and partitions of multisets.+Version: 0.2+Synopsis: Combinatorial algorithms over multisets+Description: Various combinatorial algorithms over multisets,+ including generating all permutations,+ partitions, size-2 partitions, size-k subsets,+ and Sawada's algorithm for generating all+ necklaces with elements from a multiset. Homepage: http://code.haskell.org/~byorgey/code/multiset-comb License: BSD3 License-file: LICENSE@@ -12,7 +16,10 @@ Category: Math Tested-with: GHC ==6.10.4, GHC ==6.12.1 Build-type: Simple-Cabal-version: >=1.2+Cabal-version: >=1.6+source-repository head+ type: darcs+ location: http://code.haskell.org/~byorgey/code/multiset-comb Library Exposed-modules: Math.Combinatorics.Multiset