我正在尝试编写一个可以返回用户定义集的分区集的Haskell程序。集合S的分区被定义为一组非空的,成对不相交的S子集,其并集是S.因此,[1,2,3]
返回[[[2],[3,1]],[[2,1],[3]],[[3,2,1]],[[1],[3,2]],[[1],[2],[3]]]
。我想我可以利用我刚才写的一个不同的程序,从两组中找到笛卡尔积。因此,[1,2,3] ['a', 'b']
会返回[(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b')]
。但是,我不确定如何。我认为这需要递归,如果这甚至可以适当调整。这是子集代码:
type Set a = [a]
isElement :: Eq a => a -> [a] -> Bool
isElement x [] = False
isElement x (y:ys) = if(x==y) then True else isElement x ys
subset :: Eq a => Set a -> Set a -> Bool
subset [] xs = True
subset (y:ys) xs = if(isElement y xs == True)
then do subset ys xs
else do False
答案 0 :(得分:8)
我们的想法是,为了找到集合 X ∪{ x }的所有分区,我们首先找到 X 的分区。然后以各种可能的方式向其中的每一个添加x
(即,将x
添加到分区的第一个元素,将x
添加到第二个元素等)并获取一个联合结果。
这是一个相当简单的实现:
partitions :: [a] -> [[[a]]]
partitions [] = [[]]
partitions (x:xs) = expand x $ partitions xs where
expand :: a -> [[[a]]] -> [[[a]]]
expand x ys = concatMap (extend x) ys
extend :: a -> [[a]] -> [[[a]]]
extend x [] = [[[x]]]
extend x (y:ys) = ((x:y):ys) : map (y:) (extend x ys)
答案 1 :(得分:2)
一个递归算法的伪代码:
If |S| = 1
Return ∅
Otherwise
For each nonempty proper subset X ⊂ S
Let Y = S - X
Add {X, Y} to R
For each Z in {partitionSet(X)}
Add Z ∪ {Y} to R.
Return R
由于向列表中“添加”元素不是一个非常实用的习惯用法,因此您需要使用concatMap或列表推导来执行这些步骤。您还可以将R构建为尾递归函数的累积参数,或者作为每个步骤的返回值的并集。正确的子集函数在Haskell标准库中为Data.List.subsequences
。
如果对S的所有正确子集进行了总排序,则可以使用对称性破坏来仅添加排列以外的唯一分区。也就是说,如果X> Y,你只能添加{X,Y}而不是{Y,X},只能添加{X,Y,Z}而不能添加{Y,X,Z}。请注意,您仍然只对分区中的每个集进行一次子分区!
这只找到S的分区集,如果⋃Z= X且X∪Y= S,Z和Y中所有集合的并集是S,它只返回S的非空的正确子集,以及每个分区和子分区是一组差异,因此成对不相交。
基数二的任何分区集都具有{X,SX}形式,并且算法找到它,因为它会尝试每个可能的X.基数的任何分区集 i > 2具有{ a_1,a_2,...,a_i},其中{a_1,a_2}是{a_1⋃a_2}和{{a_1⋃a_2}的分区集,...,a_i}是基数的分区集i -1,将在对搜索树的父节点进行子分区时找到。因此,通过归纳,算法找到S的所有分区集。
答案 2 :(得分:0)
最近,我又在玩set分区和haskell。即使它可能不是最快,最好的解决方案,它也能完成任务。我发现使用Data.List和List Monad可以大大减少代码量并扩大可读性。
问自己是否有一种巧妙的方法将foldl
替换为foldr
?
无论如何,这是我的解决方案:
module Main where
import Data.List
main :: IO ()
main = print $ allPart 5
insertFront :: Integer -> [[Integer]] -> [[Integer]]
insertFront k (h:t) = [k:h]++t
insertFront k _ = [[k]]
add :: Integer -> [[Integer]] -> [[[Integer]]]
add k part=zipWith (++) (inits part) (map (insertFront k) (tails part))
allPart k = foldl (>>=) [[]] [add i | i<-[1..k]]
我还想知道是否使用某些haskell库可以很短地替代insertFront。