首页 文章

Haskell学习练习给出了奇怪的结果

提问于
浏览
6

这是个问题:

“编写一个计算列表均值的函数,即列表中所有元素的总和除以其长度 . (您可能需要使用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

它's the same as the above one (with the '在哪里'关键字',但它只在这里成功,而不是在上面的那个 .
为什么?

非常感谢 .

附:
我正在从这本书中学习 - 真实世界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
不是吗?
我不明白的是什么?
谢谢 :-) .

7 回答

  • 2
    • 您第一次尝试得到的答案错误,因为您使用的公式不正确 . 垃圾进垃圾出 . (其他人已经介绍过这个 . )

    • 您可能会收到解析错误,因为某些行正在使用空格而其他行正在使用制表符 . 或者您正在使用所有选项卡,但使用非标准选项卡宽度 .

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

  • 0

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

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

    这不是你想要的 .

    第二次尝试没问题 .

  • 2

    正确:

    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
    
  • 2

    说我们定义

    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 + ...
    

    在这种情况下,延迟评估以分配所有暂停计算的形式创建更多工作,而不是在每次迭代时简单地递增计数器 .

    作为初学者, fromRationaltoRational 也可能令人困惑 . 考虑除法函数的类型:

    *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
    

    因为按列表的长度划分 .

    事实证明, IntFloatDouble 是类型类Real的实例,任何 Real 都可以转换为 Rational

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

    Rational 可能会转换为 Fractional

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

    最后,对于大型列表,我们也有机会溢出机器的双倍,但 Rational 给我们任意精度 .

    如果您具有C或Java等语言的背景,可以自动提升类型来处理这些情况,那么您会发现Haskell类型系统的这种特殊缺陷令人困惑和令人沮丧 . 我担心你只需要学会处理它 .

    完成所有这些,我们现在可以

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

    要么

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

    警告:未经测试的代码 . 在 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] .

  • 1

    哈斯克尔:最美丽的命令式语言

    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 }
    
  • 5

    设意味着x = sum(x)/ fromIntegral(length(x))

    平均值[2.0,4.0,6.0]

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

相关问题