【发布时间】:2015-04-13 20:24:22
【问题描述】:
我正在尝试通过 Project Euler(再次)自学 Haskell。问题 14 (https://projecteuler.net/problem=14) 正在乞求动态编程,从历史上看,我一直强烈反对单子(因为一再未能学会充分利用它们来让生活更轻松而不是更难)所以我试图咬一口子弹并使用 State monad 来记住我的代码......它进展不顺利。我想明确一点,我已经以简单/缓慢的方式解决了这个问题,此时我正在尝试学习一些东西(即Project Euler No. 14 Haskell 不是我想要的)。
到目前为止我的代码是:
collatzMemoCheck :: Int -> State (Map Int Int) Int
collatzMemoCheck n = state $ \s -> maybe (let (a, s') = runState (collatzFast n) s
in (a+1, Map.insert n (a+1) s'))
(\len -> (len, s))
(Map.lookup n s)
collatzFast :: Int -> State (Map Int Int) Int
collatzFast 1 = state $ \_ -> (1, Map.singleton 1 1)
collatzFast n
| even n = collatzMemoCheck (n `quot` 2)
| otherwise = collatzMemoCheck (3 * n + 1)
这适用于 cabal repl 中的单个查询,但在我的一生中,我无法弄清楚如何将重复调用 collatzFast 的状态链接起来。我想要类似的东西
-- DOES NOT WORK
allCollatzLengths = scanl (>>= collatzFast) (return Map.empty) [1..999999]
但我认为这是由内而外的。 Bind 获取上一个 State 计算的结果部分并将其传递给下一个调用,但我希望它获取上一个 State 计算的 state 部分并将 it 传递给下一个调用。
有没有正确的方法可以做到这一点,还是我把自己画到了角落里?如果我不能使用 >>=,那么拥有一个 monad 有什么意义? ...或者没有意义,因为这是一种愚蠢的方法?帮忙?
【问题讨论】:
-
我会注意到,使用 State 不是 Haskell 中记忆的惯用方式,也不是获得优雅 DP 的方式。您所说的“简单/缓慢方式”的这个问题实际上确实有很多答案使用惯用的 DP 方式来实现非常好的性能......您想使用 State 来学习如何使用它还是你对 Haskell 中的高效 DP 更感兴趣吗?
-
两者都有,但另一种方式已经很好地涵盖了。你知道为什么这不是惯用的吗?编辑:另外,我并没有把这个问题称为简单/缓慢的方式,我指的是一个天真的非 DP 实现作为简单/缓慢的方式。
-
只是出于好奇:您可以接受的速度是多少?我是一个简单的(非单子)版本,我在我的钻机上以 4.5 秒的速度通过严格的地图 - 这对你来说会慢吗?
-
我的慢版本甚至不涉及地图,它只是 collatzSlow :: Int -> [Int] 然后最大 $ map (\a -> (length $ collatzSlow a, a)) [ 1..999999]。编辑:似乎我天真的代码很慢的原因是我在 repl 中运行它。 cabal repl 花了 128.5 秒,但 cabal run 只花了 1.5 秒
-
哇,这很有趣,快速版本(由 Daniel 启用)在 repl 中需要 18 秒,而常规运行需要 2.8 秒!为什么天真的版本在运行模式下更快我什至不明白。
标签: haskell