使用Haskell从列表中传递闭包

时间:2013-10-06 18:47:01

标签: haskell transitive-closure

我需要使用Haskell在列表上生成传递闭包。

到目前为止我得到了这个:

import Data.List
qq [] = []
qq [x] = [x]
qq x = vv (sort x)

vv (x:xs) = [x] ++ (member [x] [xs]) ++  (qq xs)

member x [y] = [(x1, y2) | (x1, x2) <- x, (y1, y2) <- qq (y), x2 == y1]

输出1:

*Main> qq [(1,2),(2,3),(3,4)]
[(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]

输出2:

*Main> qq [(1,2),(2,3),(3,1)]
[(1,2),(1,3),(1,1),(2,3),(2,1),(3,1)]

问题在于第二次输出。它不是在新生成的列表上检查额外的传递闭包,而是返回结果。

为了原型haskell代码我使用了 Python代码

def transitive_closure(angel):
    closure = set(angel)
    while True:
        new_relations = set((x,w) for x,y in closure for q,w in closure if q == y)
        closure_until_now = closure | new_relations    
        if closure_until_now == closure:
            break    
        closure = closure_until_now    
    return closure

print transitive_closure([(1,2),(2,3),(3,1)])

输出:

set([(1, 2), (3, 2), (1, 3), (3, 3), (3, 1), (2, 1), (2, 3), (2, 2), (1, 1)])

这是我在Haskell函数中需要的正确输出。

如何在我的Haskell代码中执行相同的操作? (我需要从Python代码重新创建if语句到Haskell代码)

2 个答案:

答案 0 :(得分:7)

我不完全确定你在Haskell代码中想要做什么。相反,我们可以将您的Python代码移植到Haskell。

为了简单起见,让我们坚持使用列表而不是涉及集合。如果你真的需要性能,使用套装并不是那么困难;但是,如果没有一些严肃的杂技,我们就不能在Haskell中对集合使用理解¹。如果我们不介意代码较慢,我们可以使用nub²来获得与列表相同的效果。

我喜欢用类型签名开始编写函数;它让我更容易思考我正在实施的内容。我们正在列出一对并列出另一对列表。这意味着类型将大致

[(a, b)] → [(a, b)]

但是,我们希望能够使用==将对的左右部分相互比较。这意味着它们必须是相同的类型,并且必须支持==。所以实际的类型是:

transitiveClosure ∷ Eq a ⇒ [(a, a)] → [(a, a)]

现在让我们来看看你的实际算法。主要部分是while True循环。我们想将其转换为递归。考虑递归的最佳方法是将其分解为基本案例和递归案例。对于循环,这大致对应于停止条件和循环体。

那么基本案例是什么?在您的代码中,循环的退出条件隐藏在正文中。我们在closure_until_now == closure时停止。 (这是你在问题中提到的if语句,巧合。)

在函数定义中,我们可以使用 guards 指定这样的逻辑,因此递归函数的第一部分如下所示:

transitiveClosure closure 
  | closure == closureUntilNow = closure

这就像你的if声明一样。当然,我们尚未定义closureUntilNow!接下来让我们这样做。这只是一个辅助变量,所以我们在函数定义之后将它放在where块中。我们可以使用与Python代码相同的理解来定义它,使用nub来确保它保持唯一:

  where closureUntilNow = 
          nub $ closure ++  [(a, c) | (a, b) ← closure, (b', c) ← closure, b == b']

此代码与while循环中的前两行相同。

最后,我们只需要我们的递归案例。如果我们尚未完成 ,我们该怎么办?在while循环中,您只需将closure设置为closureUntilNow并再次迭代。我们将使用递归调用执行完全相同的操作:

  | otherwise = transitiveClosure closureUntilNow

由于这是模式保护的一部分,因此高于 where块。所以,把它们放在一起,我们得到:

transitiveClosure ∷ Eq a ⇒ [(a, a)] → [(a, a)]
transitiveClosure closure 
  | closure == closureUntilNow = closure
  | otherwise                  = transitiveClosure closureUntilNow
  where closureUntilNow = 
          nub $ closure ++ [(a, c) | (a, b) ← closure, (b', c) ← closure, b == b']

希望这使得编写本程序的思路变得清晰。

¹这很难,因为Set不会形成 Haskell Monad。它是一个更一般意义上的单子格,但它不符合前奏中的类。所以我们不能只使用monad理解。我们可以使用具有可重新绑定语法的monad理解来实现目标,但这不值得。

²nub是一个愚蠢命名的函数,用于从列表中删除重复项。我认为OCaml的dedup是一个很多更好的名称。

答案 1 :(得分:1)

我不知道你的haskell代码应该做什么,所以我将你的python代码逐字(尽可能接近)翻译成haskell。

import Data.List
transitive_closure angel 
    | closure_until_now == closure = closure
    | otherwise                    = transitive_closure closure_until_now

        where new_relations = nub [(x,w) | (x,y) <- closure, (q,w) <- closure, q == y] 
              closure = nub angel
              closure_until_now = closure `union` new_relations

让我们浏览一下python并分析哪一行对应于Haskell中的内容。

closure = set(angel) =&gt; closure = nub angelnub只是set

while True: =&gt;没有!在Haskell中没有'while'循环,所以递归是你唯一的选择。

new_relations = set((x,w) for x,y in closure for q,w in closure if q == y)
closure_until_now = closure | new_relations

变为

new_relations = nub [(x,w) | (x,y) <- closure, (q,w) <- closure, q == y]
closure_until_now = closure `union` new_relations

基本上是一样的。并不是声明的顺序无关紧要,因为它们不像命令式语言那样“执行”。

if closure_until_now == closure:
    break

成为

| closure_until_now == closure = closure

我假设你熟悉守卫。 If not, many before me have done a much better job explaining. python代码的语义说“如果这个条件为真,我们退出循环并转到'返回闭包'”。由于Haskell中没有循环,当您遇到退出条件时,您只需返回一个值而不是递归。

closure = closure_until_now 

成为

| otherwise                    = transitive_closure closure_until_now

如果你传递退出条件,python将继续循环。循环的下一次迭代将使用'closure'作为输入,我们将其设置为closure_until_now。因此,在Haskell版本中,'loop'的下一个'迭代'(即递归函数)将以closure_until_now作为输入。

为了更强大,您应该使用Data.SetSet唯一的问题是你不能使用列表理解。但是,您可以使用map存在的Set替换列表理解:

for = flip map
new_relations = nub $ concat $ concat $ 
                for closure (\(x,y) -> 
                    for closure (\(q,w) -> 
                                 if q == y then [(x,w)] else []
                                 )
                             )

这是一个黑客,我只是使用列表,以便“如果条件不正确”则“不返回”。在这种情况下使用类似Maybe的东西可能会更好。