【问题标题】:Partition a set in Haskell在 Haskell 中对集合进行分区
【发布时间】:2018-03-17 16:22:33
【问题描述】:

我正在尝试编写一个 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

【问题讨论】:

  • 递归。如果X可以分区,X ∪ {a}怎么分区?
  • 小风格评论:您发布的ifs 似乎是一种复杂的写作方式,分别为1)x==y || isElement x ys 和2)isElement y xs && subset ys xs。这里不需要do== True 总是多余的。

标签: haskell set partition


【解决方案1】:

这个想法是为了找到集合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

【讨论】:

  • 也可以将partitions定义为partitions = foldr expand [[]]
【解决方案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 的非空真子集的集合,并且每个分区和子分区是一个集合差异,因此成对不相交。

基数为 2 的任何分区集具有 {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的所有划分集。

【讨论】:

  • 是的,我一般不会为看起来像家庭作业的问题写完整的解决方案,但我会给出提示。
  • 如果我删除那条评论,你愿意吗?我添加它是因为(1)这是一个很酷的想法,(2)已经有一个更简单的解决方案与其他发布的答案以及(3)我并没有立即明白你的意思。
  • @Alec 这完全取决于你。
  • 鉴于两个陌生人同意你的观点,我已经删除了评论。 :)
  • @Alec 好吧,六个陌生人赞成一个不同的完整解决方案。
【解决方案3】:

最近,我又在玩 set partitions 和 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]]

我还想知道是否有一些非常短的替代 insertFront 使用一些 haskell 库。

【讨论】:

    猜你喜欢
    • 2016-07-16
    • 1970-01-01
    • 1970-01-01
    • 2022-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-10
    相关资源
    最近更新 更多