计算递归次数

时间:2010-10-13 18:16:56

标签: haskell

我有一些代码可以反复接近一个解决方案,它实际上做的并不重要,但是它通过改变mg来实现r'== rt(m猜测,从4.0开始因为我“知道”应该在球场)。

solve_m f ar st qt = solve_m' f ar st qt 4.0
  where
    solve_m' f ar st qt mg 
      | rd > precis    = f' (mg - sf)  
      | rd < (-precis) = f' (mg + sf)  
      | otherwise = mg
        where 
          f' = solve_m' f ar st qt
          rt = st + qt   
          r' = f st ar mg 
          rd = rt - r'    
          sf = abs(rd) 

我希望能够做的是计算周期数,我知道正确的方法是使用State monad,但是最优雅的方法是将put / get放入像这样的函数中这个?让f'成为阻止?或者只是添加一个计数器solve_m'并返回(counter,mg)?

谢谢!

编辑:这似乎基本上是我想要的,没有必要的Monads:

solve_m f ar st qt = (last (series), length(series))
  where
  series = takeWhile termPred (iterate solve_m' 4.0)
  termPred m' = (abs (rt - (f st ar m'))) > precis
  rt = st + qt   
  solve_m' mg 
    | rt > r' = (mg - sf)  
    | rt < r' = (mg + sf)  
      where
        r' = f st ar mg 
        rd = rt - r' 
        sf = abs(rd)

仍然看起来有点乱(重复的代码),但我会把它整理一下......这将使我接受的结果将是它将取代的代码的1/10000次迭代!

2 个答案:

答案 0 :(得分:5)

在不查看算法的情况下,执行此操作的一般方法是将终止条件与迭代算法分开:

terminationPred :: a -> Bool
algorithm :: a -> a

然后使用iterate和takeWhile:

itermediates = takeWhile (not . terminationPred) . iterate algorithm
resultAndRecursions :: a -> (a, Int)
resultAndRecursions a = (last (intermediates a), length (intermediates a) - 1)
-- you'd want to make your own safe function here, not use last and length

或展开:

intermediates = unfoldr op
  where
  op a | terminationPred a = Nothing
       | otherwise = let a' = algorithm a
                     in Just (a', a')

编辑:还注意到这两个中间体略有不同,因为第一个维持基本情况(输入a,因此- 1)而第二个没有,因此会有微小的差异在补充resultAndRecursions

答案 1 :(得分:4)

首先,您可以删除solve_m'的大多数参数:它们不会在递归调用中更改,solve_m的参数在where的范围内}子句。这也使f'函数变得不必要。

solve_m f ar st qt = solve_m' 4.0
  where
    solve_m' mg 
      | rd > precis    = solve_m' (mg - sf)  
      | rd < (-precis) = solve_m' (mg + sf)  
      | otherwise = mg
        where 
          rt = st + qt   
          r' = f st ar mg 
          rd = rt - r'    
          sf = abs(rd)

现在,solve_m'具有类型Double -> Double,因为它所做的只是执行下一次迭代,然后以递归方式完成或调用自身。碰巧的是,标准库包含一个名为iterate且函数为(a -> a) -> a -> [a]的函数,它接受这样的函数,生成迭代中每个步骤的(可能是无限的)列表。当然,所需的递归调用的数量正是结果列表的长度。在我的回答中产生了一个令人尴尬的错误。

iterate实际上做的是产生无限列表,在这种情况下,无休止地重复“最终”结果的副本。不是你想要的。我可能在考虑unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

另一个选项 - 我实际上更喜欢 - 将删除检查答案足够接近的守卫并且毕竟使用iterate,产生无限的新近似列表,然后消耗结果列出比较相邻元素,看看你有多接近。我给出了一些示例代码,但考虑到之前可能不明智的错误。

编辑:好的,为了完整起见,这里有几个简单的例子:

使用iteratetakeWhile

solve_m_iter f ar st qt = takeWhile notDoneYet $ iterate nextApprox 4.0
  where rd mg = st + qt - f st ar mg
        notDoneYet mg = abs (rd mg) > precis
        nextApprox  mg | rd mg > precis  = mg - abs (rd mg)
                       | rd mg < -precis = mg + abs (rd mg)

使用unfoldr

solve_m_unfold f ar st qt = unfoldr nextApprox
  where nextApprox mg | rd > precis  = keep $ mg - abs rd
                      | rd < -precis = keep $ mg + abs rd
                      | otherwise    = Nothing
            where rd = st + qt - f st ar mg
                  keep x = Just (x, x)

一个稍微好一点的函数来获得结果而不遍历列表两次:

getResult = foldl (\(n, _) x -> (n + 1, x)) (0, 4.0)

绝对快速而肮脏的代码,但希望有用。