首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Haskell的记忆递推功能

Haskell的记忆递推功能
EN

Stack Overflow用户
提问于 2022-07-09 00:51:25
回答 1查看 162关注 0票数 3

我有一个haskell函数试图解决这个问题:

编写一个函数'howSum( targetSum,numbers)‘,它接受一个targetSum和一个数字数组作为参数。该函数应该返回一个数组,该数组包含任何元素的组合,这些元素加起来正好等于targetSum。如果没有与targetSum相加的组合,则返回null。如果可能有多个组合,则可以返回任何一个组合。

我得到的是所有可能的数字组合。

该函数的工作方式是创建一个递归树,其中第一个调用具有总和,targetconcatMap为每个数字创建一个分支,并提供所需的剩余和。如果达到target为0,那么要获得的分支是从nums中减去数字的组合,这意味着您可以在nums中使用数字求和到目标。当返回时,在该节点上的值的结果被连接(到每个子列表)。

根据我的测试,该功能正常工作,但回忆录没有。我现在知道,memo (Map对象)在我的尝试中是无用的,因为它是不可变的,对howSumMemo的单独调用只能访问递归树中祖先的已兑现的值。如果备忘录是可变的和可引用的(在Haskell中是不可能的),它就会起作用。

代码语言:javascript
复制
import Data.Map (Map, member, findWithDefault, empty, insert)
howSumMemo :: Int -> [Int] -> Map Int [[Int]] -> [[Int]]
howSumMemo target nums memo
    | target > 0 = findWithDefault val target $ insert target val memo
    | target < 0 = []
    | otherwise = [[]]
    where val = concatMap (\x -> map (x :) (howSumMemo (target-x) nums memo)) nums

-- Memoized howSum
howSum target nums = howSumMemo target nums empty


-- Normal howSum
howSum' :: Int -> [Int] -> [[Int]]
howSum' target nums
    | target > 0 = concatMap (\x -> map (x :) (howSum' (target-x) nums)) nums
    | target < 0 = []
    | otherwise = [[]]

我怎样才能让回忆录为howSum工作呢?我试过引用https://wiki.haskell.org/Memoization

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-07-12 15:39:09

当尝试以功能方式实现有状态的东西时,所有其他方法都失败了,您可以始终使用State。下面是一个使用Control.Monad.Trans.State.Strict变压器版本

代码语言:javascript
复制
howSumState :: Int -> [Int] -> State (Map Int [[Int]]) [[Int]]
howSumState target nums
    | target > 0 = join <$> traverse (\x -> fmap (x :) <$> recurse (target - x)) nums
    | target < 0 = return []
    | otherwise = return [[]]
    where recurse x = do
            m <- gets $ Map.lookup x
            case m of
              Just res -> return res
              Nothing -> do
                res <- howSumState x nums
                modify (Map.insert x res)
                return res

回忆录数据结构仍然是一张地图。recurse函数完成了大部分的繁重工作。

它首先尝试在地图中lookup结果。Map.lookup的结果是一个Maybe值。如果它是一个Just值,它意味着结果已经在映射中,所以只需返回它。

如果lookup返回一个Nothing值,则调用howSumState以生成res值,将其放入映射中并返回。

您可以通过计算状态值来创建一个回忆录howSum函数:

代码语言:javascript
复制
-- Memoized howSum
howSum :: Int -> [Int] -> [[Int]]
howSum target nums = evalState (howSumState target nums) Map.empty

evalState只返回最终值。如果您还想查看所构建的状态,则可以使用runState

代码语言:javascript
复制
ghci> runState (howSumState 3 [1,2]) Map.empty
([[1,1,1],[1,2],[2,1]],fromList [(-1,[]),(0,[[]]),(1,[[1]]),(2,[[1,1],[2]])])

元组的第一个元素是结果:

代码语言:javascript
复制
[[1,1,1],[1,2],[2,1]]

元组的第二个元素是map,这里格式化为可读性:

代码语言:javascript
复制
Map.fromList [
    (-1, []),
    (0, [[]]),
    (1, [[1]]),
    (2, [[1,1],[2]])]

每个值都是元组,其中第一个元素是键,第二个是值。

由于我们也有非回忆录的howSum'函数,我们可以使用它作为一个测试神谕。我编写了一个QuickCheck-based属性来验证两个实现是否返回相同的值:

代码语言:javascript
复制
testProperty "Both implementations behave the same" (withMaxSuccess 10000 (do
    target <- resize 10 arbitrary
    nums <- fmap getPositive <$> arbitrary

    let actual = howSum target nums

    let expected = howSum' target nums
    return $ expected === actual))

此属性运行10,000个测试,所有测试都通过。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72918195

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档