【问题标题】:Haskell learning exercise gives strange resultsHaskell 学习练习给出了奇怪的结果
【发布时间】:2010-01-08 12:40:01
【问题描述】:

这是个问题:

"写一个函数来计算 列表的平均值,即所有的总和 列表中的元素除以其 长度。 (您可能需要使用 fromIntegral 函数转换 整数列表的长度 转换为浮点数。)”

首先我尝试了这个:

mean :: [Double] -> Double
mean []     = 0
mean (x:xs) = (x + mean xs) / (1 + mean xs)

但它给了我奇怪的结果,例如,当我这样使用它时:

mean [2,4,6]  

它给了我结果:1.41176
它应该在哪里:4

为什么?

我尝试了另一件事:

mean :: [Double] -> Double  
mean list = (summ list) / (count list)  
    where count []     = 0  
          count (x:xs) = 1 + count xs  
          summ []      = 0  
          summ (x:xs)  = x + summ xs

但是当我尝试将文件加载到 GHC 时出现错误。
错误是:

parse error on input 'count'  
Failed, modules loaded: none

再次,我做错了什么?

最后,我尝试了这个(成功):

mean :: [Double] -> Double

count []     = 0
count (x:xs) = 1 + count xs
summ  []     = 0
summ (x:xs)  = x + summ xs
mean list    = summ list / count list

和上面的一样(带有'where'关键字),但它只在这里成功,而不是在上面。
为什么?

非常感谢。

附言
我正在从这本书中学习——Real World Haskell 练习来自here——(向下滚动:-))


谢谢大家。 这是一件奇怪的事情。 当我从这里复制并测试它时,第二个示例也适用于我。 我不知道为什么昨天它对我不起作用:-)

但我仍然不明白为什么第一个不起作用。 我觉得应该是这样的


(2 + mean [4,6]) / (1 + mean [4,6])
(4 + mean [6  ]) / (1 + mean [  6])
(6 + mean [   ]) / (1 + mean [   ])      

现在是这样的


(6 +  0        )    / (1 +  0          )  -- last recursion
(4 + (6 + 0)   )    / (1 + (1 + 0)     )
(2 + (4 + (6 + 0))) / (1 + (1 + (1 + 0))

所以现在应该是:12 / 3
不是吗?
我不明白什么?
谢谢你:-)。

【问题讨论】:

  • 为什么投反对票?这是一个写得很好的问题。他说明了他必须做什么,到目前为止他已经尝试过什么,以及他的问题是什么。
  • 你的第二个例子对我有用。也许你的缩进是错误的?
  • 另外,你不需要定义自己的计数函数,你可以使用内置的“长度”函数。
  • 我知道我不需要定义计数函数,但我是初学者,想要从头开始:-)
  • 实际上,RWH 中的前两个练习是对列表求和并计算列表的长度,作为学习简单递归的一种方式。 FWIW,第二个例子也适用于我。

标签: haskell


【解决方案1】:
  1. 您在第一次尝试时得到错误的答案,因为您使用了错误的公式。垃圾进垃圾出。 (其他人已经报道过。)

  2. 您可能会遇到解析错误,因为有些行使用空格,而其他行使用制表符。或者您正在使用所有标签,但标签宽度非标准。

  3. 此处不使用或不需要缩进,因此不会出现空格 -v- 制表符问题。

【讨论】:

  • 如果您认为我的回答有任何不正确之处,请指出我所说的错误以及原因。如果您指的是我对 OP 问题第一部分的回答 --- 不,我认为重复其他人已经说过的话以及我在阅读问题。
【解决方案2】:

您的第一次尝试评估如下:

6 / 1
4 + 6 / 1 + 6
2 + (10/7) / 1 + (10/7)

这不是你想要的。

第二次尝试没问题。

【讨论】:

【解决方案3】:

正确:

import Data.List (foldl')
mean :: Fractional a => [a] -> a
mean = uncurry (/) . foldl' (\(s, n) x -> (s + x, n + 1)) (0, 0)

mean [2,4,6] = uncurry (/) $ foldl' (...) (0, 0) [2,4,6]
             = uncurry (/) $ foldl' (...) (2, 1) [4,6]
             = uncurry (/) $ foldl' (...) (6, 2) [6]
             = uncurry (/) $ foldl' (...) (12, 3) []
             = uncurry (/) (12, 3)
             = 12 / 3
             = 4

不正确:

mean [] = 0
mean (x:xs) = (x + mean xs) / (1 + mean xs)

mean [6] = mean (6 : [])
         = (6 + mean []) / (1 + mean [])
         = (6 + 0) / (1 + 0)
         = 6
mean [4,6] = mean (4 : [6])
           = (4 + mean [6]) / (1 + mean [6])
           = (4 + 6) / (1 + 6)
           = 10/7
mean [2,4,6] = mean (2 : [4,6])
             = (2 + mean [4,6]) + (1 + mean [4,6])
             = (2 + 10/7) / (1 + 10/7)
             = 24/17

【讨论】:

  • 这一切都说得通,但这不是一条相当复杂的路吗? (用 uncurry 和 lambda 表达式做什么)OP 对 Haskell 来说似乎很新,所以我只是认为一个更简单的答案会更好地为他服务。
  • 感谢您的回答:-)。我认为当我们再次开始时(在我们从递归中退出之后),划分就完成了。在您的示例中,我看到每次从一次递归中退出,我们都会进行除法,除法的结果将传递到下一次递归。我对吗?我不明白的另一件事是为什么最终结果是 24/17?你是怎么从 (2 + 10/7) / (1 + 10/7) 变成 24/17 的?请解释:-)
  • 是的。递归没有什么特别之处。它恰好是对与您现在所在的名称相同的函数的调用。最后一点只是简单的数学运算:(2+10/7)/(1+10/7) = (24/7)/(17/7) = 24/7
  • 在“是”中,您的意思是我的第一个问题是正确的?我的意思是:每次从一个递归退出,我们都会做除法,除法的结果会传递到下一次递归? (抱歉唠叨,但我想确定一下:-))。关于简单的数学,我很抱歉,但似乎即使是简单的数学我也不明白:-D。但我的朋友帮助了我,我最终明白了。非常感谢:-)。
  • 第一个问题是。你可以想象你定义了很多函数:mean1调用mean2调用mean3,它们完全不相关。我打错了评论(我在上面的答案中得到了正确的答案),但是您没有在小学学习如何除分数吗?
【解决方案4】:

假设我们定义

naivemean l = sum l / fromIntegral (length l)

它有几个严重的限制。首先,定义不包括Int的列表:

*Main> naivemean ([1] :: [Int])

<interactive>:1:0:
    No instance for (Fractional Int)
      arising from a use of `naivemean' at <interactive>:1:0-21
    Possible fix: add an instance declaration for (Fractional Int)
    In the expression: naivemean ([1] :: [Int])
    In the definition of `it': it = naivemean ([1] :: [Int])

其次,它会破坏大型列表的堆栈:

*Main> naivemean [1..1000000]
*** Exception: stack overflow

此外,它会在列表中进行两次传递,一次传递给sum,另一次传递给length,当一次传递就可以完成时。

我们可以解决所有三个问题

{-# LANGUAGE BangPatterns #-}

mean :: (Real a, Fractional b) => [a] -> b
mean = go (toRational 0) 0
  where
    go !s !l []     = fromRational s / fromIntegral l
    go  s  l (x:xs) = go (s+.x) (l+1) xs
    s +. x = s + toRational x

Bang patterns 强制对标记的参数进行严格评估。如果没有刘海,上面的代码在给定一个长列表时也会破坏堆栈,但原因不同:例如 l 的惰性求值会生成一个长的未求值链的形式

0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + ...

在这种情况下,与在每次迭代时简单地增加计数器相比,惰性求值以分配所有挂起计算的形式创建的工作更多。

作为初学者,fromRational 和 toRational 可能也会让人感到困惑。考虑除法函数的类型:

*Main> :t (/)
(/) :: (Fractional a) => a -> a -> a

这意味着为Fractional 实例的任何两个相同类型的值定义了除法。 Int 不是这些类型之一:

*Main> (1::Int) / (2::Int)

<interactive>:1:0:
    No instance for (Fractional Int)
      arising from a use of `/' at <interactive>:1:0-18
    Possible fix: add an instance declaration for (Fractional Int)
    In the expression: (1 :: Int) / (2 :: Int)
    In the definition of `it': it = (1 :: Int) / (2 :: Int)

mean 的一个定义应该涵盖所有 [Int]、[Float] 和 [Double],但没有理性位(并且没有类型注释),推断为 mean 的类型是

*Main> :t mean
mean :: [Double] -> Double

因为除以列表的长度。

原来Int、Float和Double是类型类Real的实例,任何Real都可以转换为Rational

*Main> :t toRational
toRational :: (Real a) => a -> Rational

而Rational 可以转换为Fractional:

*Main> :t fromRational
fromRational :: (Fractional a) => Rational -> a

最后,对于大型列表,我们也有可能溢出机器 double,但 Rational 给了我们任意精度。

如果您具有自动提升类型以处理这些情况的 C 或 Java 等语言的背景,您会发现 Haskell 类型系统的这种特殊的不灵活性令人困惑和沮丧。恐怕你只需要学会处理它。

做完这一切,我们现在可以

*Main> mean ([1..1000000] :: [Int])
500000.5

或

*Main> mean ([1..1000000] :: [Double])
500000.5

【讨论】:

  • 感谢您的回答:-)。我是新手,所以我不明白你输入的一些内容,但也许在将来,当我(希望如此)完成这本书时,我会明白的:-)
  • 不客气!当您需要对不同数字类型的值执行算术运算时,请记住此信息。
【解决方案5】:

警告:前面有未经测试的代码。在您对mean 的定义中,您需要准确地携带运行总和和运行长度,正如其他人指出的那样,您没有这样做。像下面这样的东西应该可以工作:

mean0 sum len [] = sum / len
mean0 sum len (x:xs) = mean0 (sum+x) (len+1) xs

此定义传递两个累加器,每个累加器用于运行总数和运行计数,当您沿列表递归时会更新它们。当函数最终用完要处理的列表(基本情况)时,它只会进行所需的计算。

要使用mean0,你只需写

mean0 0 0 [2, 4, 6]

如您所见,为累加器提供初始值有点烦人,因此通常提供类似的包装器

mean xs = mean0 0 0 xs

现在,您可以随意写mean [2, 4, 6]。

【讨论】:

  • SO 的设计者希望阻止在函数和变量名称中使用 ',因此他们以使用奇怪的颜色为代码着色为代价。我建议坚持他们的风格并尽量避免在代码 sn-ps 中使用'。
  • 嗯,在 Haskell 中使用带底号的名称至少有些普遍,我想我没有仔细查看预览,以真正注意到突出显示的变化。
  • 这似乎是最简单的答案,可以有效地满足 OP 的要求。
  • 感谢您的回答。我检查了它,它正在工作:-)。另外,请让我看看我是否理解您的回答。在这个例子中,我认为,我们没有从递归中退出,并且当我们处于内部递归时函数完成了。那里(在内部递归中) (((0 + 2) + 4) + 6) 的 thunk - 这是 sum 变量 - 是计算,并且 (((0 + 1) + 1) + 1) 的 thunk - 这是 len 变量 - 也是计算。在计算了 sum 和 len 之后,除法就变成了现实,我们得到了答案。我对吗? :-)。
【解决方案6】:

Haskell:最漂亮的命令式语言

import Control.Monad.ST
import Data.STRef 
import Control.Monad

mean xs = s / l  
    where (s,l) = runST $ do{
       acc <- newSTRef (0,0);
       forM_ xs $ \x -> do{
          modifySTRef acc $  \(a,b) -> (x+a,1+b)
       };
       readSTRef acc }

【讨论】:

【解决方案7】:

让 x = sum (x) / fromIntegral(length(x))

平均 [2.0, 4.0, 6.0]

当然,这必须改进(空列表,确实适用于双打...)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-02
    • 1970-01-01
    • 2021-03-17
    相关资源
    最近更新 更多