【问题标题】:Insert value into a map and then return it?将值插入地图然后返回?
【发布时间】:2015-02-23 23:34:02
【问题描述】:

我是一名 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 函数返回它?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    你不能。 collLength 的值是不可变的,定义后不能修改。为了拥有“状态”,您必须使用支持它的 monad,或者您可以在某些情况下使用惰性来充当记忆(见脚注)。在这种情况下,最简单的方法可能是使用 State 库中的 State 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 长度,而后者将返回一个包含长度和记忆图的元组。请注意,我已从使用 Int 更改为 Integer 以允许任意大的值。

    一个示例用法是

    > evalCollatz 100
    25
    > evalCollatz 4029438019378410983794857103401801934908745019384013795092745080123949028750238401290701092387401894671234110985710984671039485712039584
    3269
    

    在我的计算机上,这几乎可以立即执行(:set +s 说它需要 0.03 秒,但这不是一种准确的分析技术)。

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

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

    这将保留调用 collatz 之间的状态。

    脚注:

    在某些情况下,可以更容易地利用惰性来避免像 state monad 这样的解决方案。最常见的例子是生成整个斐波那契数列:

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

    此递归定义将使用“记忆化”构建所有斐波那契数的无限惰性列表,因为它使用先前值的结果来计算下一个值。

    【讨论】:

      【解决方案2】:

      为了补充 bhelkir 的出色答案,在 Haskell 中进行记忆的常用方法是定义一个 数据结构(通常是 trie),将参数值与其结果相关联。如果有无限多个可能的参数值,这将是一个无限大的数据结构,但是由于惰性求值,这样的结构在 Haskell 中是可行的。

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

      还有提供通用记忆的库:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-11
        • 1970-01-01
        • 2019-09-10
        相关资源
        最近更新 更多