Haskell尾递归用于多调用函数

时间:2015-02-11 21:40:30

标签: haskell tail-recursion

这是非尾递归函数

alg :: Int -> Int
alg n = if n<7 then n else alg(n-1) * alg(n-2) * alg(n-4) * alg(n-6)

我已经坚持了一段时间,我得到了尾递归的基本概念,以及如何为单个调用递归函数做到这一点,但是没有线索如何为多调用函数做这个。

甚至提出了这种可憎的行为

algT :: Int -> Int
algT n = tail1 n 0 where tail1 i r = tail1(i-1) r *
         tail2 n 0 where tail2 i r = tail2(i-2) r *
         tail3 n 0 where tail3 i r = tail3(i-4) r *
         tail4 n 0 where tail4 i r = tail4(i-6) r

它不起作用,显然不是递归函数应该如何看,只有很少的其他尝试,但所有这些都以无限的100%cpu加载循环结束...

4 个答案:

答案 0 :(得分:3)

你有没有看过Haskell的斐波那契?它是一种类似的功能。 BTW尾递归在Haskell中并不是一个非常正确的术语,因为多递归函数不能以递归方式完成,但Haskell的懒惰性质使得类似但更强大的技巧成为可能。这是给出的标准:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

在你的上使用相同的技巧给EDIT:作为一个函数

alg :: Int -> Int
alg n = alg' !! (n - 1)
    where alg' = 1 : 2 : 3 : 4 : 5 : 6 : zipWith4 (\a b c d -> a * b * c * d) (drop 5 alg') (drop 4 alg') (drop 2 alg') alg'

请注意,您不应在此使用Int,这不是开放式的,第11个字词将在Int中循环。

编辑:实际上Int比我想象的更糟糕。一旦你在你的结果中达到32 2,你将开始返回0,因为每个答案都是0 mod 2 ^ 32。

答案 1 :(得分:2)

从你的问题来看,并不完全清楚使你的函数尾部重新驱动的目的是什么。如果你试图减少CPU /内存使用量,那么你应该使用memoization(在Guvante的回答中提到)。

同时,有一种方法可以使几乎任何函数都是尾递归的,称为continuation-passing style。您在CPS中编写的示例如下所示:

alg_cps :: Integer -> (Integer->a) -> a
alg_cps n cont = 
    if n < 7 
    then cont n 
    else alg_cps (n - 1) 
        (\x1 -> alg_cps (n - 2) 
            (\x2 -> alg_cps (n - 4) 
                (\x3 -> alg_cps (n - 6)
                    (\x4 -> cont (x1*x2*x3*x4)))))

要直接获得结果,您可以使用id作为延续来调用它:

alg_cps 20 id

请注意,与天真的非尾递归实现相比,这不会降低算法复杂性或内存使用率。

答案 2 :(得分:0)

我认为我有一个解决方案,但它不是很优雅或漂亮。

alg :: Int -> Int
alg n | n < 7     -> n
      | otherwise -> alg' n (repeat 0)

alg' :: Int -> [Int] -> Int
alg' n [] = error "something has gone horribly wrong"
alg' n l@(x:y)
  | n < 5     -> error "something else has gone horribly wrong"
  | n == 6    -> product $ zipWith (^) [6,5..1] l
  | otherwise -> alg' (n-1) $ zipWith (+) [x,x,0,x,0,x] (y ++ [0])

我们的想法是,您可以跟踪您应该将每个事物相乘多少次而不实际进行任何计算直到最后。在任何给定的时间,您都可以获得有关您需要接下来的6个值中的任何一个的信息,一旦您低于7,您只需将1-6提升到适当的权力并获取他们的产品。

(我实际上并没有对此进行测试,但看起来似乎是对的。即使不是,我也很确定它背后的想法是合理的)

P.S。正如@Guvante所说,Int在这里不是一个好选择,因为它会迅速溢出。作为一般规则,我默认使用Integer,只有在我有充分理由时才会切换。

答案 3 :(得分:0)

这是一个可能的解决方案。

let f = [1..6] ++ foldr1 (zipWith (*)) [f, drop 2 f, drop 4 f, drop 5 f]

甚至:

let f = [1..6] ++ foldr1 (zipWith (*)) (map (flip drop $ f) [0,2,4,5])