【问题标题】:Why am I getting a stack overflow?为什么会出现堆栈溢出?
【发布时间】:2017-04-09 16:49:10
【问题描述】:

所以,我尝试实现我自己的 ModExp 实现(我知道我应该使用现有的,但这是学校的任务)并且在使用大数字时出现堆栈溢出 (>= 2^1024)

modExp :: Integer -> Integer -> Integer -> Integer
modExp x y m
    | y == 0 = 1
    | y `mod` 2 == 0 = modExp ((x ^ 2) `mod` m) (y `div` 2) m
    | otherwise = x * modExp x (y - 1) m `mod` m

有什么问题?听说必须使用尾递归来避免堆栈溢出,但是这里我有两种情况必须调用递归。我该如何解决?

【问题讨论】:

  • 摆脱非尾递归的常用技巧是强制将您需要对函数结果执行的任何操作推到函数本身。在您的情况下,您需要将结果乘以 x,因此添加一个额外的参数并将您的因子传递给它。内部相乘,但在递归调用之前。

标签: haskell recursion


【解决方案1】:

你的函数是单递归的,所以如果我们确保它也是尾递归的,我们可以保持它的内存大小。诀窍是不要产生更出色的操作。守卫在y 中使函数变得严格,所以我们知道这不是等待发生的任意计算树。但是x 随调用深度而变化,在我们产生返回值之前不需要,并且返回值有三种情况:常量、尾递归和在调用外有两个操作(*mod)的递归.这些操作将在某种调用堆栈上进行。

解决这个问题可能需要两步操作:首先,使函数完全尾递归。其次,让它变得严格,这样它就可以急切地求值,而不是构建越来越复杂的表达式。

尾递归变体:

modExp :: Integer -> Integer -> Integer -> Integer
modExp x y m = modExp' x y m 1
    where
        modExp' x y m acc
            | y == 0 = acc
            | y `mod` 2 == 0 = modExp' ((x ^ 2) `mod` m) (y `div` 2) m acc
            | otherwise = modExp' x (y - 1) m (x * acc `mod` m)

在某些情况下,在 xacc 中强制执行严格性也可能很有用。这可以通过seq 完成:

modExp :: Integer -> Integer -> Integer -> Integer
modExp x y m = modExp' x y m 1
    where
        modExp' x y m acc
            | y == 0 = acc
            | y `mod` 2 == 0 = let x' = (x ^ 2) `mod` m
                                   y' = y `div` 2
                               in x' `seq` modExp' x' y' m acc
            | otherwise = let acc' = x * acc `mod` m
                          in acc' `seq` modExp' x (y - 1) m acc'

一个稍微简单的变体可以将seq 应用到xacc 本身,原则上这可能会留下一层计算未完成,但仅此而已。无论哪种方式都强制执行恒定空间。

不幸的是,我没有使用 ghc 7.10.3 重现您的堆栈溢出,所以我不太确定哪些长度会被证明是必要的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-18
    • 2012-12-19
    • 1970-01-01
    • 2012-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多