2013-10-13 27 views
16

我在Haskell中列出了一些對象。我需要找出這些對象是否滿足某些條件。所以,我寫了以下內容:顯示Haskell程序的進度

any (\x -> check x) xs 

但問題是檢查操作非常昂貴,並且列表相當大。我想查看運行時當前的進度,例如50% (1000/2000 checked).
我該怎麼做?

+0

如果您正在做大數據集的繁重工作,請勿使用列表。列表適合學習,但在現實世界中表現不佳應用程序 – Ankur

+0

此外,只要找到與條件匹配的元素,任何元素都會返回,在這種情況下,顯示處理總元素中的元素的進度並不合理 – Ankur

+0

@Ankur它有道理,爲什麼不呢?它可以被解釋爲'30%的元素已經被處理,並且沒有發現任何合適的元素' – pfedotovsky

回答

16

既然你想看到你的函數的進展(這是函數的副作用),最明顯的解決方案是使用monads。所以,我們要做的第一件事就是讓any功能的單子版本:

anyM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool 
anyM _ []  = return False 
anyM pred (x:xs) = reduce (pred x) xs 
    where reduce acc []  = acc 
      reduce acc (x:xs) = do 
       condition <- acc 
       if condition 
        then return condition 
        else reduce (pred x) xs 

上述功能anyMany功能的單子版本。它允許我們除了檢查給定列表中的任何項是否滿足給定的謂詞外,還會產生副作用。

我們可以使用anyM函數來創建一個顯示進度條除執行any功能如下副作用的另一個功能:

anyVar :: (a -> Bool) -> [a] -> IO Bool 
anyVar pred xs = anyM check $ zip [1..] xs 
    where check (n,x) = do 
      putStrLn $ show n ++ " checked. " 
      return $ pred x 

注意,因爲我們不知道的長度我們只會顯示列表中選中的項目數量。如果我們知道在列表中的項目數事先那麼我們就可以顯示更翔實的進度條:

anyFix :: (a -> Bool) -> Int -> [a] -> IO Bool 
anyFix pred length xs = anyM check $ zip [1..] xs 
    where check (n,x) = do 
      putStrLn $ show (100 * n `div` length) ++ "% (" ++ 
       show n ++ "/" ++ show length ++ " checked). " 
      return $ pred x 

使用anyVar功能無限列表和列表的長度你不事先知道。將anyFix函數用於事先知道其長度的有限列表。

如果列表很大,並且您事先不知道列表的長度,那麼length函數將需要遍歷整個列表以確定其長度。因此,最好使用anyVar代替。

最後把它包起來,這一切都是你將如何使用上述功能:

main = anyFix (==2000) 2000 [1..2000] 

你的情況,你可以做,而不是執行以下操作:

main = anyVar check xs 

希望這個答案幫你。

+1

優秀的答案,但你的anyM做兩次檢查每個元素是不好的原因有兩個:首先,如果檢查費用昂貴,我們不希望將工作量增加一倍,因爲它是一次性的,可能會注意到它被稱爲兩次例如一個國家曾經追蹤處理了多少元素將會被混淆。更好的替換你的if if if condition then return condition else reduce(pred x)xs'。 – Jedai

+0

@傑得好看。我從未注意過它。也許是因爲'\ ESC [2K \ ESC [0G'''每次擦除線路。我很困惑 - 爲什麼執行兩次?解釋會很棒。另外由於某種原因,我的'anyVar'和'anyFix'函數會在'putStr'上產生一個解析錯誤。但是,如果我用'putStr「字符串>> return(pred x)'替換'do'塊,那麼它按預期工作。你會碰巧知道爲什麼發生這種情況? –

+0

@Jedai我想出了爲什麼'anyVar'和'anyFix'引發了一個解析錯誤:這是由於不正確的縮進。我仍然想知道爲什麼我的原始'anyM'函數爲每個項目評估兩次'pred'。 –

7

最天真的和直接的方法就是實現自己的

anyM :: (a -> Bool) -> [a] -> IO Bool 

,打印進度條(例如使用terminal-progress-bar)。

但請注意,爲了計算百分比,您必須評估完整列表。這打破了懶惰,並可能對程序的空間行爲產生不良影響。

也有使用unsafePerformIOunsafeInterleaveIO的方法,它允許您監視純計算(例如any),請參見bytestring-progress的示例。但是,這是可疑的設計,你應該只使用,如果你知道你明白的後果。

11

這樣做的另一種方法是使用流式庫,如conduitpipes。下面是使用pipes一些示例代碼,其中每個列表的元素到達被檢查一次打印點:(each是從管模塊的功能)

import Pipes 
import qualified Pipes.Prelude as P 

bigList :: [Int] 
bigList = [1,2,3,4] 

check :: Int -> Bool 
check = (>3) 

main :: IO() 
main = do 
    result <- P.any check $ each bigList >-> P.chain (\_ -> putStrLn ".") 
    putStrLn . show $ result 

現在,如果你想要顯示百分比,P.chain (\_ -> putStrLn ".")部分管道將不得不變得更智能。它必須將當前的百分比作爲狀態,並且知道列表的長度。 (如果你的列表非常龐大而且生成時間很長,計算它的長度會迫使它的評估,並且可能會導致問題,如果你已經在內存中使用它,這將不是什麼大問題。是以前的代碼的可能的擴展,實際顯示百分比:

{-# LANGUAGE FlexibleContexts #-} 

import Pipes 
import qualified Pipes.Prelude as P 
import Data.Function 
import Control.Monad.RWS 

bigList :: [Int] 
bigList = [1,2,3,4] 

check :: Int -> Bool 
check = (>3) 

-- List length is the environment, number of received tasks is the state. 
tracker :: (MonadReader Int m, MonadState Int m, MonadIO m) => Pipe a a m r 
tracker = P.chain $ \_ -> do 
    progress <- on (/) fromIntegral `liftM` (modify succ >> get) `ap` ask 
    liftIO . putStrLn . show $ progress 

main :: IO() 
main = do 
    (result,()) <- evalRWST (P.any check $ each bigList >-> tracker) 
          (length bigList) -- list length as unchanging environment 
          0 -- initial number of received tasks (the mutable state) 
    putStrLn . show $ result 

它可以進一步細化,只顯示顯著百分比的增加。

+0

我知道列表的長度,所以這不是問題。 – pfedotovsky

+1

如果您知道長度,Array看起來會比List更好。 –

3

我只想用Debug.Trace.trace並跟蹤當前位置,像這樣:

any (\(i,x) -> trace (showProgress i (length xs)) $ check x) $ zip [1..] xs 
+0

'trace'是一種依賴'unsafePerformIO'的黑客。由於模塊名稱暗示這僅用於調試。切勿將其用於生產代碼! – Lemming

0

您可以使用庫明確的例外,像explicit-exception:Control.Monad.Execption.Synchronoustransformers:Control.Monad.Trans.Maybe和「拋出一個異常」當你發現一個元素通過支票。