【问题标题】:How to calculate how many recursive calls happened in this Haskell function?如何计算这个 Haskell 函数中发生了多少递归调用?
【发布时间】:2023-03-11 12:38:01
【问题描述】:

我已经尝试了几个小时来解决这个问题。 我需要计算使用某个函数发生了多少递归调用:

maximum' :: (Ord a) => [a] -> a  
maximum' [] = error "maximum of empty list"  
maximum' [x] = x  
maximum' (x:xs)   
    | x > maxTail = x  
    | otherwise = maxTail  
    where maxTail = maximum' xs  

在此先感谢

【问题讨论】:

  • 这不是最方便的事情,但是您可以更改您的函数以获取一个额外的参数,您将在其中存储当前的递归深度,并返回元组(actualResult, finalRecursionDepth)
  • 这其实是我需要的。你能详细说明一下吗? @迈克尔
  • 我已经写了一个答案。将它放在评论中有点太大了(即使最大评论长度允许)。

标签: haskell recursion


【解决方案1】:

这是对 Michailuser2297560 答案的题外话。

如果我们可以重用原始实现并以某种方式“检测”它,而不是从头重写函数以添加跟踪功能,该怎么办?

我们可以写一个基础函数

  • 是单子的,但在单子上是多态的。
  • fix 的帮助下使用匿名递归定义。

例如:

import Data.Function(fix)
import Data.Functor.Identity

maximumAux :: (Monad m,Ord a) 
           => ([a] -> m a) 
           ->  [a] -> m a  
maximumAux _ [] = error "maximum of empty list"  
maximumAux _ [x] = return x  
maximumAux recurse (x:xs) = 
    do maxTail <- recurse xs
       return (max x maxTail)

maximumPure :: Ord a => [a] -> a
maximumPure as = runIdentity (fix maximumAux as)

然后像这样检测它,重用原始逻辑:

maximumInstrumented :: (Ord a, Show a) => [a] -> IO a
maximumInstrumented = fix (instrument maximumAux)
    where
    instrument auxf iorecurse as = 
        do print $ "starting call with params " ++ show as
           a <- auxf iorecurse as
           print $ "ending call with value" ++ show a
           return a

但也许将函数定义为“默认情况下是一元的”不太实用。

【讨论】:

    【解决方案2】:

    你喜欢函数式编程吗?你喜欢命令式编程吗?为什么不能两者兼得!这是一种计算递归深度的递归命令式方法。

    {-# LANGUAGE FlexibleContexts #-}
    
    import Control.Monad.State
    
    maximumWithCount :: (Ord a, MonadState Int m) => [a] -> m a
    maximumWithCount xs = case xs of
      [] -> error "empty list"
      [x] -> return x
      (x:xs) -> do
        modify (+1)  -- increment the counter because we're recursing!
        y <- maximumWithCount xs
        return $ max x y
    
    λ runState (maximumWithCount [1,2,3,4,5,4,3,2,1]) 0
    (5,8)
    

    【讨论】:

    • 使用Writer (Sum Int) 可能比State Int 更合适
    【解决方案3】:

    您可以重写您的函数以在额外的参数中携带有关递归深度的信息,并将其作为元组的第二个(或第一个,如果您愿意的话)元素返回:

    maximum' :: (Ord a) => [a] -> Int -> (a, Int)
    maximum' [] _ = error "maximum of empty list"  
    maximum' [x] n = (x, n)
    maximum' (x:xs) n   
        | x > fst maxTail = (x, snd maxTail)
        | otherwise = maxTail  
        where maxTail = maximum' xs (n + 1)
    

    然后您可以使用maximum' lst 0maximum' lst 1 调用函数,具体取决于您是否希望第一次调用计为递归级别。

    可以使用任何递归函数来完成,但在您的情况下可能没有必要。正如 chepner 所写,无需额外计算即可知道答案。

    【讨论】:

    • 携带深度对于信息目的很有用,在某些情况下,对于实现功能是必要的。 (想到树的有界深度优先搜索。)
    【解决方案4】:

    对于带有n 元素的列表,有n-1 递归调用。为了便于查看,我们将稍微重写该函数。 (我们可以忽略空列表。)

    maximum' [x] = x
    maximum' (x:xs) = if x > maxTail then x else maxTail
                      where maxTail = maximum' xs
    

    当参数的尾部不为空时进行递归调用。 (请注意,为空列表定义的函数也会对具有 n 项的列表进行 n 递归调用。)

    我们可以将调用扩展为 3 项列表,例如:

    maximum' [1,2,3] = if 1 > maxTail then 1 else maxTail where maxTail = maximum' [2,3]
     maximum' [2,3] = if 2 > maxTail then 2 else maxTail where maxTail = maximum' [3]
      maximum' [3] = 3
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-03
      • 2014-01-25
      • 2016-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-21
      • 2020-03-12
      相关资源
      最近更新 更多