【问题标题】:Haskell - C stack overflowHaskell - C 堆栈溢出
【发布时间】:2015-11-19 02:38:13
【问题描述】:

我刚刚被介绍给 Haskell,所以我对如何用该语言编写代码不是很熟练,因此,如果这是重复的,很抱歉,但是我不理解用该语言编写的其他代码。

我正在尝试编写一个算法,该算法将采用不超过给定值的正偶数之和。我试图编写代码,但是我得到了 C 堆栈溢出错误。

这是我的代码:

sumint :: Int -> Int
sumint x
  | x==0 = 0  
  | x==1 = 1
  | (x `mod` 2 == 0) && (x >= 2) = x + (sumint x-2)
  | (x `mod` 2 /= 0) && (x >= 1) = x + (sumint x-1)

我哪里出错了?

【问题讨论】:

    标签: algorithm haskell


    【解决方案1】:

    初始错误:无限递归

    单步执行您的代码:

    sumint :: Int -> Int
    

    嘿,类型签名。你摇滚。

    sumint x
      | x==0 = 0
    

    一个基本案例,很酷。

      | x==1 = 1
    

    完全没有必要的情况。好吧,当然……除了。 1 甚至不是,为什么我们要把它包括在总和中?它应该为零(或完全删除)。

      | (x `mod` 2 == 0) && (x >= 2) = x + (sumint x-2)
    

    问题的关键就在这里。 1. X 是偶数 - 很棒。 2. X 是正数,是的。结果是x + (sumint x) - 2否!

    • 错误 1:注意函数应用程序绑定得比运算符更紧密,所以这应该是 x + sumint (x-2)

    这就是堆栈溢出的原因。 sumint 2 == 2 + (sumint 2) - 2 + (sumint 2) -2 + (sumint 2) -2 + ...,是的,无限递归。

      | (x `mod` 2 /= 0) && (x >= 1) = x + (sumint x-1)
    

    另一种情况...几率...肯定...但是为什么我们要添加 x 呢?您想添加偶数,而不是赔率。所以在解决上述问题的同时我们得到:

    • 错误 2:如果您确定x 是奇数,请不要添加x。只需使用sumint (x-1)

    那么你就没有案例了。如果 x 不是正数会发生什么?你需要(另一个)案例。

    | otherwise = 0
    

    下一期:无积累

    现在的问题是您正在构建一个大型 thunk(未评估的计算),而不是通过在您的进展过程中累积结果来在恒定空间中操作。请注意,如果我们将您的计算扩展为 6,我们会得到:

    sumint 6 = 6 + sumint (6-2) 
          = 6 + 4 + sumint (4-2)
          = 6 + 4 + 2 + sumint (2-2)
          = 6 + 4 + 2 + 0
    

    您真的不想将所有这些添加分开,最好传入一个累加器,例如:

    sumint x = go x 0
     where
      go n accumulator
             | n <= 0    = accumulator
             | odd n     = go (n-1) accumulator
             | otherwise = go (n-2) (accumulator + n)
    

    旁注:其他 stackoverflow 公民可能会提到使累加器严格,这是一种很好的形式。我不想用这里的讨论分散当前提问者的注意力。请注意,使用优化 -O2 就足够了。

    惯用解决方案

    上述所有解决方案都相当冗长。使用函数和累加器迭代列表的一般操作是fold 的类型。折叠是函数式编程中常见的许多高度优化的结构遍历之一。在这种情况下,“严格的左折叠”是典型的候选者(来自Data.List)。即foldl'' 素数 (') 按照惯例意味着它是严格的,而l 意味着左。

    sumint n = foldl' (+) 0 [val | val <- [0..n], even val]
    

    在这里,我们将列表折叠起来得到我们的总和。为了创建感兴趣的列表,我们使用了列表推导 - 首先枚举来自 0..n 的值并跳过任何不符合谓词 even 的值。

    我们可以通过使用sum 函数和列表推导进一步清理和改进它,每步2 步,从而只为我们提供您想要的事件:

    sumint n = sum [0,2..n]
    

    【讨论】:

    • 小细节:go 应在使用 foldl'sumint 的定义中替换为 (+)。我会进行此编辑,但更改少于 6 个字符。
    【解决方案2】:

    这是一个运算符优先级问题。在 Haskell 中,函数应用程序具有最高的优先级。因此,当您编写sumint x - 2 时,即使您将其解释为sumint (x-2),Haskell 也会将其解释为(sumint x) - 2。因此——您试图直接根据sumint x 定义sumint x——这只会堆积递归函数调用,直到堆栈溢出。如果要在函数应用程序之前评估减法,则需要添加显式括号。

    【讨论】:

      猜你喜欢
      • 2011-08-22
      • 2019-07-08
      • 2015-08-05
      • 1970-01-01
      • 1970-01-01
      • 2012-12-20
      • 2011-03-02
      • 2014-01-17
      相关资源
      最近更新 更多