我需要使用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代码)
答案 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 angel
。 nub
只是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.Set。 Set
唯一的问题是你不能使用列表理解。但是,您可以使用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
的东西可能会更好。