将值插入地图然后返回?

时间:2014-12-26 18:53:01

标签: haskell

我是一名Haskell新手试图解决有关不同数字的Collat​​z序列长度的问题。我希望用memoization编写一个函数,但是我被卡住了:

-- map for memoization
collLength:: Data.Map.Map Int Int
collLength = Data.Map.fromList [(1,0)]

-- collatz n returns the length of the sequence starting in n
collatz:: Int -> Int
collatz n =
    case (Data.Map.lookup n collLength) of
        Just x -> x
        Nothing ->
            let result
                | n `mod` 2 == 0    = 1 + collatz (n `div` 2)
                | otherwise         = 1 + collatz (3*n + 1)
            in do
                Data.Map.insert n result collLength
                return result 

我想用新计算的结果填充地图。但是,我收到以下错误:

  

无法匹配类型' Data.Map.Map Int Int'与' Int'

     

预期类型:Data.Map.Map Int Int - > Data.Map.Map Int Int - > INT

     

实际类型:Data.Map.Map Int Int - > Data.Map.Map Int Int - > Data.Map.Map Int Int

来自最后一个街区。如何插入新值然后让collat​​z函数返回它?

2 个答案:

答案 0 :(得分:3)

要添加到bhelkir的优秀答案,在Haskell中进行memoization的常用方法是定义一个数据结构(通常是trie),它将参数值与其结果相关联。如果存在无限多个可能的参数值,那么这将是一个无限大的数据结构,但由于延迟评估,这种结构在Haskell中是可行的。

他给出的fibs示例是一个这样的数据结构 - 每个斐波纳契数通过出现在无限列表的相应索引处与其对应的参数值相关联。但通常最好使用某种搜索树表示来获得O(log(n))搜索时间。 This SO entry对此有更深入的了解。

还有一些提供通用记忆的库:

答案 1 :(得分:2)

你做不到。值collLength是不可变的,在定义之后无法修改。为了拥有“状态”,你必须使用支持它的monad,或者你可以在某些情况下使用懒惰来表现得像记忆(见脚注)。在这种情况下,最简单的方法是使用State库中的mtl monad:

import Control.Monad.State
import Data.Map (Map)
import qualified Data.Map as M

-- "State s a" means that "s" is the stateful value
-- and "a" is the return value of a monadic operation
collatz :: Integer -> State (Map Integer Integer) Integer
collatz n = do
    -- Get the current memoization map
    collLength <- get
    -- look up the current n as before
    case M.lookup n collLength of
        Just x -> return x
        Nothing -> do
            -- Calculate either collatz (n `div` 2) or collatz (3 * n + 1)
            -- and increment the result by 1
            result <- fmap (1+) $
                if even n
                    then collatz (n `div` 2)
                    else collatz (3 * n + 1)
            -- Modify the current memoization map to include our new result
            modify (M.insert n result)
            return result

evalCollatz :: Integer -> Integer
evalCollatz n = evalState (collatz n) $ M.singleton 1 0

然后可以使用evalStaterunState运行此功能。前者将返回collat​​z长度,而后者将返回包含长度和memoized地图的元组。请注意,我已经从使用Int更改为Integer以允许任意大的值。

示例用法是

> evalCollatz 100
25
> evalCollatz 4029438019378410983794857103401801934908745019384013795092745080123949028750238401290701092387401894671234110985710984671039485712039584
3269

在我的计算机上,这几乎立即执行(:set +s表示花了0.03秒,但这不是一种准确的分析技术)。

如果您想计算一系列数字的collat​​z长度,那么您可以使用monadic组合器mapM

> maximum $ evalState (mapM collatz [1..100000]) $ M.singleton 1 0
350

这将保留对collatz

的调用之间的状态

脚注:

对于某些情况,懒惰可以更容易被利用来避免像州monad这样的解决方案。最常见的例子是生成整个斐波纳契序列:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

这个递归定义将使用“memoization”构建所有斐波纳契数的无限懒惰列表,因为它使用先前值的结果来计算下一个值。