使用Haskell从列表中传递闭包

使用Haskell从列表中传递闭包,haskell,transitive-closure,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] *Main> qq [(1,2),(2,

我需要使用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]
*Main> qq [(1,2),(2,3),(3,4)]
[(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]
*Main> qq [(1,2),(2,3),(3,1)]
[(1,2),(1,3),(1,1),(2,3),(2,1),(3,1)]
set([(1, 2), (3, 2), (1, 3), (3, 3), (3, 1), (2, 1), (2, 3), (2, 2), (1, 1)])
输出2:

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]
*Main> qq [(1,2),(2,3),(3,4)]
[(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]
*Main> qq [(1,2),(2,3),(3,1)]
[(1,2),(1,3),(1,1),(2,3),(2,1),(3,1)]
set([(1, 2), (3, 2), (1, 3), (3, 3), (3, 1), (2, 1), (2, 3), (2, 2), (1, 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)])
输出:

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]
*Main> qq [(1,2),(2,3),(3,4)]
[(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]
*Main> qq [(1,2),(2,3),(3,1)]
[(1,2),(1,3),(1,1),(2,3),(2,1),(3,1)]
set([(1, 2), (3, 2), (1, 3), (3, 3), (3, 1), (2, 1), (2, 3), (2, 2), (1, 1)])
这是我在Haskell函数中需要的正确输出


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

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

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

我喜欢用类型签名开始编写函数;这使我更容易准确地思考我正在实现什么。我们正在获取一个配对列表并生成另一个配对列表。这意味着类型大致为:

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

transitiveClosure ∷ Eq a ⇒ [(a, a)] → [(a, a)]
现在让我们看看实际的算法。主要部分是
while True
循环。我们想把它转换成递归。考虑递归的最佳方式是将其分解为基本情况和递归情况。对于循环,这大致对应于停止条件和循环体

那么基本情况是什么呢?在代码中,循环的退出条件隐藏在主体内部。我们在
closure\u时停止,直到现在==closure
。(巧合的是,这是您在问题中提到的if声明。)

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

transitiveClosure closure 
  | closure == closureUntilNow = closure
这就像您的
if
语句一样。当然,我们还没有定义
closureNow
!那么,让我们下一步做这个。这只是一个helper变量,所以我们把它放在函数定义之后的
块中。我们可以使用与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
。从更一般的意义上讲,它是一个单子,但它不符合前奏曲中的等级。所以我们不能只使用单子理解。我们可以使用具有可重新绑定语法的单子理解来达到目的,但这并不值得


²
nub
是一个名称愚蠢的函数,它从列表中删除重复项。我认为OCaml的重复数据消除是一个更好的名称。

我不知道您的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
变成

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

| closure_until_now == closure = closure
| otherwise                    = transitive_closure closure_until_now
我假设你熟悉警卫。python代码的语义表示“如果此条件为真,我们将退出循环并转到‘返回闭包’”。由于Haskell中没有循环,当遇到退出条件时,只需返回一个值,而不是递归

closure = closure_until_now 
变成

| closure_until_now == closure = closure
| otherwise                    = transitive_closure closure_until_now
如果通过退出条件,python将继续循环。循环的下一次迭代将使用“closure”作为其输入,我们将其设置为
closure\u直到现在。因此,在Haskell版本中,“循环”(即递归函数)的下一次“迭代”将使用
closure\u直到现在
作为输入

要获得更高的健壮性,应该使用。
Set
的唯一问题是不能使用列表理解。但是,您可以用
map
s替换列表理解,这是为
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
之类的名称。

为什么在Python中使用描述性名称,而在Haskell中使用神秘的速记?类型签名也是一个好主意,不管它多么琐碎。@leftaroundabout,我知道这是一个不好的做法,但我会在弄明白这一点后更改变量/函数名和类型签名。希望这不会太难阅读此代码。