让我以身作则澄清我的问题。这是在Scala中使用尾递归编写的标准取幂算法:
def power(x: Double, y: Int): Double = {
def sqr(z: Double): Double = z * z
def loop(xx: Double, yy: Int): Double =
if (yy == 0) xx
else if (yy % 2 == 0) sqr(loop(xx, yy / 2))
else loop(xx * x, yy - 1)
loop(1.0, y)
}
此处sqr
方法用于生成loop
结果的平方。看起来不是一个好主意 - 为这样一个简单的操作定义一个特殊的功能。但是,我们不能只编写loop(..) * loop(..)
,因为它会使计算加倍。
我们也可以使用val
并且不使用sqr
函数来编写它:
def power(x: Double, y: Int): Double = {
def loop(xx: Double, yy: Int): Double =
if (yy == 0) xx
else if (yy % 2 == 0) { val s = loop(xx, yy / 2); s * s }
else loop(xx * x, yy - 1)
loop(1.0, y)
}
我不能说它看起来比使用sqr
的变体更好,因为它使用state variable
。第一种情况更具功能性,第二种方式更适合Scala。
无论如何,我的问题是当你需要后处理函数的结果时如何处理案例?也许Scala还有其他方法可以实现这一目标吗?
答案 0 :(得分:6)
您使用的是
法律x^(2n) = x^n * x^n
但这与
相同x^n * x^n = (x*x)^n
因此,为避免递归后的平方,y为偶数的情况下的值应如下面的代码清单所示。
通过这种方式,可以进行尾调用。这是完整的代码(不知道Scala,我希望通过类比得到语法):
def power(x: Double, y: Int): Double = {
def loop(xx: Double, acc: Double, yy: Int): Double =
if (yy == 0) acc
else if (yy % 2 == 0) loop(xx*xx, acc, yy / 2)
else loop(xx, acc * xx, yy - 1)
loop(x, 1.0, y)
}
这是一种类似Haskell的语言:
power2 x n = loop x 1 n
where
loop x a 0 = a
loop x a n = if odd n then loop x (a*x) (n-1)
else loop (x*x) a (n `quot` 2)
答案 1 :(得分:5)
您可以使用“前进管道”。我从这里得到了这个想法:Cache an intermediate variable in an one-liner。
所以
val s = loop(xx, yy / 2); s * s
可以改写为
loop(xx, yy / 2) |> (s => s * s)
使用像这样的隐式转换
implicit class PipedObject[A](value: A) {
def |>[B](f: A => B): B = f(value)
}
正如彼得指出的那样:使用隐式价值等级
object PipedObjectContainer {
implicit class PipedObject[A](val value: A) extends AnyVal {
def |>[B](f: A => B): B = f(value)
}
}
像这样使用
import PipedObjectContainer._
loop(xx, yy / 2) |> (s => s * s)
更好,因为它不需要临时实例(需要Scala> = 2.10)。
答案 2 :(得分:2)
在我的评论中,我指出你的实现不能进行尾调用优化,因为在yy % 2 == 0
的情况下,有一个不在尾部位置的递归调用。因此,对于大输入,这可能会溢出堆栈。
对此的一般解决方案是 trampoline 您的函数,用可以使用“后处理”(例如sqr
)映射的数据替换递归调用。然后由解释器计算结果,解释器逐步执行返回值,将它们存储在堆而不是堆栈中。
Scalaz库提供了数据类型和解释器的实现。
import scalaz.Free.Trampoline, scalaz.Trampoline._
def sqr(z: Double): Double = z * z
def power(x: Double, y: Int): Double = {
def loop(xx: Double, yy: Int): Trampoline[Double] =
if (yy == 0)
done(xx)
else if (yy % 2 == 0)
suspend(loop(xx, yy / 2)) map sqr
else
suspend(loop(xx * x, yy - 1))
loop(1.0, y).run
}
但是,这样做会带来相当大的性能影响。在这种特殊情况下,我会使用Igno的解决方案来避免需要调用sqr
。但是,当您无法对算法进行此类优化时,上述技术非常有用。
答案 3 :(得分:0)
在这个特殊情况下
最后只需要一个独立的递归调用 - 总是给出尾递归
def power(x: Double, y: Int): Double =
if (y == 0) x
else {
val evenPower = y % 2 == 0
power(if (evenPower) x * x else x, if (evenPower) y / 2 else y - 1)
}