让我们说我想为Levensthein distance(编辑距离)实现通常的动态编程算法。提出递归非常容易:
editDistance [] ys = length ys
editDistance xs [] = length xs
editDistance (x:xs) (y:ys)
| x == y = editDistance xs ys
| otherwise = minimum [
1 + editDistance xs (y:ys),
1 + editDistance (x:xs) ys,
1 + editDistance xs ys]
这会受到指数运行时间的影响,因此需要记住该功能。我想通过使用Data.Memocombinators这样做,我尝试了几种方法。这是我目前的尝试:
import qualified Data.MemoCombinators as M
memLength str =
M.wrap
(\i -> drop i str)
(\xs -> length str - length xs)
(M.arrayRange (0,length str))
elm xs ys = (M.memo2 memListx memListy editDistance) xs ys
where
memListx = memLength xs
memListy = memLength ys
然而,memoization似乎没有任何影响,虽然我希望任何memoization对运行时间有明显的改善,因为它至少是多项式的。我的实施有什么问题?如何在尽可能保留编辑距离的高级定义的同时获得正常的运行时间?
答案 0 :(得分:3)
如果您发布的代码实际上是您正在做的事情,那么您做错了! :-)。如果要记住递归函数,则需要将对递归版本的调用调用回memoized版本。所以例如:
editDistance (x:xs) (y:ys)
| x == y = elm xs ys
| ...
否则,您执行完整的递归计算并仅存储最终结果。您需要存储中间结果。
这里还有另一个问题。 elm的memo表不应该依赖于它的参数(理想情况下你甚至不应该提到参数,因此你不依赖于编译器足够智能来计算依赖关系)。如果备注表依赖于参数,那么您必须为每个不同的参数构建一个新表,并且我们需要为所有参数共享一个表。你可以尝试一些愚蠢的事情,就像记住参数的整个结构一样:
elm = M.memo2 (M.list M.char) (M.list M.char)
看看是否加快了速度(加入前一招)。然后,您可以继续尝试仅使用长度而不是整个列表结构,以获得额外的提升。
希望有所帮助。