使用“通过平方取幂”可以“应用n次函数”吗?

时间:2014-08-29 02:44:49

标签: haskell functional-programming

给定f :: a -> a类型的函数,我们可以生成一个适用f n次的函数:

nTimes :: Int -> (a -> a) -> (a -> a)
nTimes 0 _ = id
nTimes 1 f = f
nTimes n f = f . nTimes (n-1) f

我可以在这里使用exponentiating by squaring方法来实现另一个nTimes函数:

nTimes' :: Int -> (a -> a) -> (a -> a)
nTimes' = nTimes'' id
    where
        nTimes'' acc n f
            | n == 0    = acc
            | even n    = nTimes'' acc (n `div` 2) (f . f)
            | otherwise = nTimes'' (acc . f) (n-1) f

我的问题是:

  • nTimesnTimes'是否会产生相同的结果?
  • nTimes'会更快吗?

3 个答案:

答案 0 :(得分:8)

虽然等同于,但如果在任何实际情况下ntimes'实际上更快或节省内存,我会感到非常惊讶。问题是,与x * x通过平方加倍普通求幂不同,f . f实际上并不共享在应用f时完成的任何真实工作。最后还是将最外面的单f应用于由所有余数以某种方式构造的参数。并且ntimes (n-1) f x将是关于你可以拥有的最紧凑的表示,直到它本身实际上需要被评估,这将需要将最左边的f应用于代表ntimes (n-2) f x等。

编辑:让我补充一点,如果您正在进行 memoization ,这可能会发生显着变化,即将f . f替换为memo (f . f),以修改一个修改要记住的函数的memo-combinator结果。在这种情况下,可以共享实际工作,这个版本的ntimes' 可能有时候会有所改进。其他时候,它可能会浪费大量的内存。

答案 1 :(得分:6)

在两种情况下都会产生相同的结果,因为*.都是关联运算符。

然而,“加速”并不是你想到的加速。通过平方取幂是好的,因为它减少了*运算符的应用次数,从线性到对数次数。在这种情况下,您将减少应用.运算符的次数,从线性到对数次数。

然而,像ØrjanJohansen所说,与*不同,.运算符并没有真正做太多 - 它只需要两个函数值,并输出一个新的函数值,它基本上包含了给出了两个函数加上一些代码。

nTimes'获得的结果函数,当应用于某个值时,必须仍然运行f n次。因此,实际上运行结果函数没有任何改进,只是使用. 构造结果函数的过程得到了改进。

答案 2 :(得分:4)

  

nTimes和nTimes'总是产生相同的结果?

是。 (除非你有bug,否则不检查)。

  

nTimes'更快?

可能不显着。 f本身在两种情况下都是共享的,因此没有重新计算。

如果f不够懒惰,那么您正在构建一个对f的引用列表与一个共享f引用的树的引用,因此您在这里节省了一些内存