所以今晚我正在考虑图距离算法,并提出了这个问题 当我开车的时候:
module GraphDistance where
import Data.Map
distance :: (Ord a) => a -> Map a [a] -> Map a Int
distance a m = m'
where m' = mapWithKey d m
d a' as = if a == a' then 0 else 1 + minimum (Prelude.map (m'!) as)
起初,我为自己感到骄傲,因为它看起来很优雅。 但后来我意识到它不起作用 - 核心运行可能会卡住 在循环中。
我必须编写代码才能说服自己:
ghci> distance 1 $ fromList [(0,[1]),(1,[0,2]),(2,[1,3]),(3,[2])]
fromList [(0,1),(1,0),(2,^CInterrupted.
但现在我觉得我已经考虑过了。
是否有常见错误和反模式列表 在处理我可以阅读的核心数据时, 所以我可以训练我的大脑去思考?经验 通过非核心思考,我已经很好地训练了我 数据,但如果可以的话,我想向别人学习错误。
答案 0 :(得分:13)
嗯,在处理corecursive数据时,实际上只有一个基本错误,那就是不小心使用递归!
Corecursion意味着在某种意义上数据是以递增方式生成的。你的图形距离函数在这里是有启发性的,因为它似乎应该工作,所以想想增量部分应该在哪里:起点是从节点到自身的距离0,否则一个更大比它自己的近邻之间的最小距离。因此,我们希望每个距离值是增量的,这意味着我们需要它们本身适当地是自发的。
由于(+)
和minimum
的组合,问题的递归正在发生:当找到最小值时,1
将始终小于1 + n
,无需担心n
是什么......但是如果不计算整个值,就无法比较Int
。
简而言之,该算法希望能够只根据需要比较(1 +)
应用0
的次数;也就是说,它希望使用“零”和“后继”定义惰性自然数。
看哪:
data Nat = Z | S Nat
natToInt :: Nat -> Int
natToInt Z = 0
natToInt (S n) = 1 + natToInt n
instance Show Nat where show = show . natToInt
instance Eq Nat where
Z == Z = True
(S n1) == (S n2) = n1 == n2
_ == _ = False
Z /= Z = False
(S n1) /= (S n2) = n1 /= n2
_ /= _ = True
instance Ord Nat where
compare Z Z = EQ
compare Z (S _) = LT
compare (S _) Z = GT
compare (S n1) (S n2) = compare n1 n2
然后在GHCi中:
> distance 1 $ fromList [(0,[1]),(1,[0,2]),(2,[1,3]),(3,[2])]
fromList [(0,1),(1,0),(2,1),(3,2)]
证明您的算法有效 [0] ;你的实现是不正确的。
现在,稍微改变一下,让我们将您的算法应用到不同的图表中:
> distance 1 $ fromList [(0,[1]),(1,[0]),(2,[3]),(3,[2])]
......我们对此有何看法?节点1与节点2或3的距离是多少?
在GHCi中运行它有明显的结果:
fromList [(0,1),(1,0),(2,^CInterrupted.
然而,算法在此图上正常工作。你能看到问题吗?为什么它挂在GHCi?
总之,您需要清楚地区分两种不能自由混合的形式:
两种形式都可以以结构无关的方式进行转换(例如,map
可以在有限和无限列表上工作)。 Codata可以通过核心递归算法逐步消耗;数据可以递归生成,受递归算法的限制。
你不能做的是递归地使用codata(例如,左折叠无限列表)或者生成数据(由于懒惰而在Haskell中很少见)。
[0] :实际上,它会在某些输入上失败(例如,某些断开连接的图形),但这是一个不同的问题。