2017-10-15 75 views
0

我有懷疑要做這個練習。我有解決方案(這顯然是錯誤的),但我不明白:計算平均值和最大值的Haskell IO程序

編寫一個程序,從默認輸入設備讀取整數,每行一個,負的或零的宇宙,並打印平均和最大的讀取值。

我的代碼:

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我怎麼辦的疑問。

回答

2

雖然有點偏離主題,但我認爲我會評論關注點和模塊性的分離。

通常,我們會盡量保持程序的純粹部分與不純(IO)部分分開。

我們可以讀取帶有不純代碼的Int s的列表,然後使用純函數處理它以查找最大值,和和長度以計算平均值。

下面,readInts讀取從stdin Int s,至它讀取非正值,在列表中返回正Int S(在IO)。 maxSumLength將當前處理的元素的當前最大值,總和和長度作爲元組處理,然後處理下一個元素,並返回一個新的元組,並摺疊下一個元素。最後,main讀取Int s的列表,並使用maxSumLength(0, 0, 0)的初始狀態應用嚴格的左側摺疊(foldl')來計算最終的最大值,總和和長度。然後打印總和和長度的最大值和平均值。

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) 

該代碼比以前更模塊化。我們可以在需要列表Int s的其他程序中重新使用readInts。此外,算法的純粹部分不再關心Int的列表來自哪裏。但是,這個代碼存在問題。當以這種方式編寫時,即使處理代碼在到達時消耗輸入,整個列表也必須在純代碼開始處理之前緩衝在內存中。

這是conduit軟件包可以提供幫助的地方。 conduit包允許通過不純的Source生成一個流,並將其連接到純粹的Consumer,並允許純代碼與不純代碼交錯。 conduit-combinators包提供的組合器允許將流處理得像列表一樣(特別是,foldlC允許我們對導管流執行嚴格的左摺疊而不是列表)。

在下面的代碼,所述readInts函數現在是IntSource A S,在IO單子運行。它使用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,使他們在創建Int s的消耗不純readInts,但不犧牲模塊化。該流可用於其他需要Ints流的程序,純代碼仍不再關心Int是從哪裏來的。

1

雖然不是最優的,但您的程序幾乎可以正常工作。這裏有一些小的修復和清理。我儘可能保留原始代碼,即使可能有更好的解決方案。

首先,您正在使用未定義的cAverage。這個錯誤可以很容易地修復。

average2參數是沒有意義的,因爲它是未使用的 - 讓我們刪除它。

一些let可以移動到我們實際使用這些變量的分支。

我們還可以使用條件執行次要重構並計算新的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)