繁体   English   中英

Kotlin 中的递归“装饰器”

[英]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进行比较,后者第一次运行速度很快,并使用042之间的值填充备忘录。

在 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)
    }
}

这里有一些可能需要证明的决定:

  1. 我使用驼峰命名而不是 PascalCase。 尽管object是 class,但它被称为 function,所以我觉得 function 的命名约定更好。 有了这个,您可以像往常一样调用fibonacci那契 function。

  2. 我已将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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM