【问题标题】:Show progress of Haskell program显示 Haskell 程序的进度
【发布时间】:2013-10-13 09:29:48
【问题描述】:

我在 Haskell 中有一些对象的列表。我需要找出这些对象中的某个人是否满足某些条件。所以,我写了以下内容:

any (\x -> check x) xs

但问题是检查操作非常昂贵,而且列表很大。我想查看运行时的当前进度,例如50% (1000/2000 checked).
我该怎么做?

【问题讨论】:

  • 如果您正在处理大型数据集的繁重工作,请不要使用 List。列表有利于学习,但在现实应用中表现不佳
  • 另外,any 会在找到符合条件的元素后立即返回,在这种情况下,显示处理了多少元素的进度没有意义
  • @Ankur 有道理,为什么不呢?可以解释为'30%的元素已经被处理,没有找到合适的'
  • 我的意思是,一般来说,当你显示一个进度时,用户希望它一直到 100%,而不是突然退出,比如说 10%。但是,如果这只是为了让您以这种方式测试某些东西,那就没问题了
  • @Ankur,这只是给我的,是的。

标签: haskell functional-programming


【解决方案1】:

由于您想查看函数的进度(这是函数的副作用),最明显的解决方案是使用 monad。所以首先要做的是制作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

希望这个答案对你有所帮助。

【讨论】:

  • 很好的答案,但你的 anyM 对每个元素进行两次检查,这有两个原因:首先,如果检查很昂贵,我们不想加倍工作量,其次是因为它是一元的,它可能会注意到它被调用了两次,例如用于跟踪处理了多少元素的 State 会被混淆。最好用 if condition then return condition else reduce (pred x) xs 替换你的 if。
  • @Jedai 不错。我以前从未注意到它。也许是因为"\ESC[2K\ESC[0G" 每次都删除了这条线。我很困惑 - 为什么它执行两次?一个解释会很棒。此外,由于某种原因,我的anyVaranyFix 函数在putStr 上引发了解析错误。但是,如果我用 putStr "string" &gt;&gt; return (pred x) 替换 do 块,那么它会按预期工作。你会知道为什么会发生这种情况吗?
  • @Jedai 我知道为什么anyVaranyFix 会引发解析错误:这是由于缩进不正确。我仍然想知道为什么我原来的 anyM 函数对每个项目评估 pred 两次。
【解决方案2】:

另一种方法是使用流媒体库,如conduitpipes。下面是一些使用pipes 的示例代码,它会在每次列表元素到达要检查时打印一个点:

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

each 是 Pipes 模块中的一个函数。)

现在,如果您想显示百分比,管道的P.chain (\_ -&gt; 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

可以进一步细化,只显示显着的百分比增长。

【讨论】:

  • 我知道列表的长度,所以没问题。
  • 如果您知道长度,Array 似乎是比 List 更好的选择。
【解决方案3】:

最天真最直接的方式就是自己实现

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

打印进度条(例如使用terminal-progress-bar)。

但请注意,为了计算百分比,您必须评估完整列表。这会破坏惰性,并可能对程序的空间行为产生不良和不良影响。

还有一些使用unsafePerformIOunsafeInterleaveIO 的方法允许您监控纯计算(例如any),请参阅bytestring-progress 示例。但这是一个可疑的设计,只有在您知道自己了解后果的情况下才应该使用它。

【讨论】:

    【解决方案4】:

    我只会使用Debug.Trace.trace 并像这样跟踪当前位置:

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

    【讨论】:

    • trace 是一个依赖于unsafePerformIO 的hack。正如模块名称所暗示的,这仅用于调试。永远不要将它用于生产代码!
    【解决方案5】:

    您可以将库用于显式异常,例如 explicit-exception:Control.Monad.Execption.Synchronoustransformers:Control.Monad.Trans.Maybe,并在找到通过检查的元素时“抛出异常”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多