[英]Recursive "decorator" in Kotlin
假设我有一个像fibonacci
那契这样的递归 function :
fun fibonacci(n: Int): BigInteger =
if (n < 2)
n.toBigInteger()
else
fibonacci(n-1) + fibonacci(n-2)
这很慢,因为我多次重新计算已知值。 我可以通过添加“备忘录”来解决这个问题:
val memo = ConcurrentSkipListMap<Int, BigInteger>()
fun mFibonacci(n: Int): BigInteger =
memo.computeIfAbsent(n) {
if (n < 2)
it.toBigInteger()
else
mFibonacci(n-1) + mFibonacci(n-2)
}
工作起来很有魅力,但我可以在不接触 function 的情况下做到这一点吗? 我的第一个想法是使用包装器 class:
class Cached<in T, out R>(private val f: (T) -> R) : (T) -> R {
private val cache = ConcurrentSkipListMap<T, R>()
override fun invoke(x: T): R = cache.computeIfAbsent(x, f)
}
cFibonacci = Cached(::fibonacci)
...但问题是,这只会记住最外层的调用。 如果我用像42
这样的“大”数字调用cFibonacci
,它会花费很长时间,然后将正确的值放入备忘录中; 随后调用42
会很快,但41
会再次变慢。 将此与mFibonacci
进行比较,后者第一次运行速度很快,并使用0
到42
之间的值填充备忘录。
在 Python 中,我可以编写一个“装饰器”来执行此操作。
def memoized(f):
def helper(t):
if x in helper.memo:
return helper.memo[t]
else:
r = f(t)
helper.memo[t] = r
return r
helper.memo = {}
return helper
@memoized
def fib(n):
if n < 2:
return n
else:
return fib(n-1) + fib(n-2)
这就像上面的mFibonacci
一样工作。 如果我从其他地方导入fib
并且无法访问定义,我也可以将其称为fib = memoized(fib)
。 有趣的是, c_fib = memoized(fib)
的工作方式类似于上面的Cached
/ cFibonacci
,暗示 function 引用的可变性可能是必要的。
问题是:(如何)我可以像在 Python 中那样影响 Kotlin 中的内部调用的方式包装/“装饰”递归 function?
在没有解决方案的情况下,我会建议一个解决方法。 此模式需要访问 function 的定义(即它不能是导入):
object fibonacci: (Int) -> BigInteger {
private val memo = ConcurrentSkipListMap<Int, BigInteger>()
override fun invoke(n: Int): BigInteger = fibonacci(n)
private fun fibonacci(n: Int): BigInteger = memo.computeIfAbsent(n) {
if (n < 2)
n.toBigInteger()
else
fibonacci(n-1) + fibonacci(n-2)
}
}
这里有一些可能需要证明的决定:
我使用驼峰命名而不是 PascalCase。 尽管object
是 class,但它被称为 function,所以我觉得 function 的命名约定更好。 有了这个,您可以像往常一样调用fibonacci
那契 function。
我已将invoke
重命名为fibonacci
。 没有这个,递归调用使用invoke
,这对我来说似乎不太可读。 有了它,您就可以像往常一样读取和写入斐波那fibonacci
function(几乎*)。
通常,想法是尽可能减少干扰,同时仍然添加所需的功能。 不过,我愿意接受有关如何改进它的建议!
*需要注意的是 function 是使用 lambda 语法定义的,因此没有return
。 如果在 function 的末尾有一个 return,则只需删除return
关键字即可。 如果你有多个回报,你将不得不使用不太漂亮的return@computeIfAbsent
来处理短路。
根据要求,这是@AlexJones 解决方法的一种变体,它不会将 function 包装在具有非常规命名的object
中。 我没有对此进行测试——它基于其他解决方案有效的假设。 以下代码位于 a.kt 文件的顶层。
private val memo = ConcurrentSkipListMap<Int, BigInteger>()
fun fibonacci(n: Int): BigInteger = fibonacciImpl(n)
private fun fibonacciImpl(n: Int): BigInteger = memo.computeIfAbsent(n) {
if (n < 2)
n.toBigInteger()
else
fibonacci(n-1) + fibonacci(n-2)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.