【问题标题】:Haskell IO program that calculates mean and highest value计算平均值和最大值的 Haskell IO 程序
【发布时间】:2017-10-15 17:26:27
【问题描述】:

我对做这个练习有疑问。我有解决方案(这显然是错误的)但我无法理解:

编写一个程序,从默认输入设备(每行一个)读取整数到负值或零值域,并打印读取值的平均值和最大值。

我的代码:

a6 ::Int -> Float ->Int->Int-> IO()
a6 cBigger average2 sum2 cCount = do
 c <- getLine
 let digit = read c :: Int
 let sum = sum2 + digit
 let average = fromIntegral sum2/ fromIntegral cCount
 if (digit <=0)
     then putStrLn("Bigger :" ++show(cBigger)++ "average "++show(cAverage))
     else
     if digit > cBigger
          then a6 digit average sum (cCount+1)
           else a6 cBigger average sum (cCount+1)

由于我对 Haskell 不太了解,所以我不知道该怎么做。

【问题讨论】:

    标签: haskell io functional-programming


    【解决方案1】:

    虽然有点离题,但我想我会评论关注点分离和模块化。

    通常,我们会尝试将程序的纯部分与不纯 (IO) 部分分开。

    我们可以读取包含不纯代码的Ints 列表,然后使用纯函数对其进行处理以找到最大值、总和和长度以计算平均值。

    下面,readInts 从标准输入读取Ints,直到它读取一个非正值,在列表中返回正的Ints(在IO 中)。 maxSumLength 获取目前处理的元素的当前最大值、总和和长度 作为一个元组,以及要处理的下一个元素,并返回一个包含下一个元素的新元组。最后,main 读取 Ints 的列表,并使用 @ 应用严格的左折叠 (foldl') 987654331@ 和 (0, 0, 0) 的初始状态来计算最终的最大值、总和和长度。然后它会根据总和和长度打印最大值和平均值。

    module Main where
    
    import Data.List ( foldl' )
    
    readInts :: IO [Int]
    readInts = do
      i <- read <$> getLine
      if i <= 0
        then return []
        else (i:) <$> readInts
    
    maxSumLength :: (Int, Int, Int) -> Int -> (Int, Int, Int)
    maxSumLength (m, s, l) x = (max m x, s+x, l+1)
    
    main :: IO ()
    main = do
      (m, s, l) <- foldl' maxSumLength (0, 0, 0) <$> readInts
      putStrLn $ "max=" ++ show m ++ ", avg=" ++ show (fromIntegral s / fromIntegral l)
    

    此代码比以前更加模块化。我们可以在需要Ints 列表的其他程序中重用readInts。此外,算法的纯部分不再关心Ints 列表的来源。但是,此代码存在问题。以这种方式编写时,必须在纯代码开始处理之前将整个列表缓冲在内存中,即使处理代码可以在输入到达时使用它。

    这就是conduit 包可以提供帮助的地方。 conduit 包允许由不纯的Source 生成流并连接到纯的Consumer,并允许纯代码与不纯代码交错。 conduit-combinators 包提供了组合器,允许像列表一样对待流(特别是,foldlC 允许我们对管道流而不是列表执行严格的左折叠)。

    在下面的代码中,readInts 函数现在是在 IO monad 中运行的 Ints 的 Source。它使用repeatWhileMC 组合器来执行循环和终止测试。纯maxSumLength不变;但是,在main 中,我们不使用foldl',而是使用foldlC 折叠管道流。

    module Main where
    
    import Conduit
    
    readInts :: Source IO Int
    readInts = repeatWhileMC (read <$> getLine) (> 0)
    
    maxSumLength :: (Int, Int, Int) -> Int -> (Int, Int, Int)
    maxSumLength (m, s, l) x = (max m x, s+x, l+1)
    
    main :: IO ()
    main = do
      (m, s, n) <- runConduit (readInts =$= foldlC maxSumLength (0, 0, 0))
      putStrLn $ "max=" ++ show m ++ ", avg=" ++ show (fromIntegral s / fromIntegral n)
    

    此代码将纯maxSumLength 与不纯readInts 交错,以便Ints 在创建时被使用,但不会牺牲模块化。 readInts流可以用在其他需要Ints流的程序中,纯代码仍然不再关心Ints来自哪里。

    【讨论】:

      【解决方案2】:

      虽然不是最佳方案,但您的程序几乎可以运行。这是一些小的修复和清理。我尽量保留您的原始代码,即使可能有更好的解决方案。

      首先,您使用的是未定义的cAverage。这个错误很容易修复。

      average2 参数没有意义,因为它没有被使用——让我们删除它。

      一些lets 可以移动到我们实际使用这些变量的分支。

      我们还可以执行次要重构并使用条件计算新的bigger 值,而不是使用条件来执行两个不同的递归调用。 (不过,使用max 函数会更好。)

      考虑将“更大”重命名为“最大”、“最大”或“最大”。对我来说听起来更好。

      a6 :: Int -> Int -> Int -> IO()
      a6 bigger oldSum count = do
       c <- getLine
       let digit = read c :: Int
       if digit <= 0
           then let average = fromIntegral oldSum / fromIntegral count :: Double
                in putStrLn ("Bigger: " ++ show bigger ++ ", average: " ++ show average)
           else let newBigger = if digit > bigger then digit else bigger
                    newSum = oldSum + digit
                in a6 newBigger newSum (count+1)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-04
        • 2014-11-20
        • 2014-10-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-01
        相关资源
        最近更新 更多