在Haskell中对一组进行分区

时间:2017-10-05 22:55:25

标签: haskell set partition

我正在尝试编写一个可以返回用户定义集的分区集的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

3 个答案:

答案 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)

演示: https://ideone.com/ClYOoQ

答案 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。