pat*_*rit 48 scope scala memoization dynamic-programming forward-reference
我想记住这个:
def fib(n: Int) = if(n <= 1) 1 else fib(n-1) + fib(n-2)
println(fib(100)) // times out
Run Code Online (Sandbox Code Playgroud)
所以我写了这个,这令人惊讶地编译和工作(我很惊讶,因为fib在其声明中引用自己):
case class Memo[A,B](f: A => B) extends (A => B) {
private val cache = mutable.Map.empty[A, B]
def apply(x: A) = cache getOrElseUpdate (x, f(x))
}
val fib: Memo[Int, BigInt] = Memo {
case 0 => 0
case 1 => 1
case n => fib(n-1) + fib(n-2)
}
println(fib(100)) // prints 100th fibonacci number instantly
Run Code Online (Sandbox Code Playgroud)
但是当我尝试在a中声明fib时def,我得到一个编译器错误:
def foo(n: Int) = {
val fib: Memo[Int, BigInt] = Memo {
case 0 => 0
case 1 => 1
case n => fib(n-1) + fib(n-2)
}
fib(n)
}
Run Code Online (Sandbox Code Playgroud)
以上无法编译 error: forward reference extends over definition of value fib
case n => fib(n-1) + fib(n-2)
为什么声明val fib内部def失败但在类/对象范围之外工作?
为了澄清,为什么我可能想要在def范围中声明递归memoized函数 - 这是我对子集求和问题的解决方案:
/**
* Subset sum algorithm - can we achieve sum t using elements from s?
*
* @param s set of integers
* @param t target
* @return true iff there exists a subset of s that sums to t
*/
def subsetSum(s: Seq[Int], t: Int): Boolean = {
val max = s.scanLeft(0)((sum, i) => (sum + i) max sum) //max(i) = largest sum achievable from first i elements
val min = s.scanLeft(0)((sum, i) => (sum + i) min sum) //min(i) = smallest sum achievable from first i elements
val dp: Memo[(Int, Int), Boolean] = Memo { // dp(i,x) = can we achieve x using the first i elements?
case (_, 0) => true // 0 can always be achieved using empty set
case (0, _) => false // if empty set, non-zero cannot be achieved
case (i, x) if min(i) <= x && x <= max(i) => dp(i-1, x - s(i-1)) || dp(i-1, x) // try with/without s(i-1)
case _ => false // outside range otherwise
}
dp(s.length, t)
}
Run Code Online (Sandbox Code Playgroud)
pat*_*rit 34
我找到了一种更好的使用Scala进行memoize的方法:
def memoize[I, O](f: I => O): I => O = new mutable.HashMap[I, O]() {
override def apply(key: I) = getOrElseUpdate(key, f(key))
}
Run Code Online (Sandbox Code Playgroud)
现在你可以写如下斐波那契:
lazy val fib: Int => BigInt = memoize {
case 0 => 0
case 1 => 1
case n => fib(n-1) + fib(n-2)
}
Run Code Online (Sandbox Code Playgroud)
这是一个有多个参数(选择函数):
lazy val c: ((Int, Int)) => BigInt = memoize {
case (_, 0) => 1
case (n, r) if r > n/2 => c(n, n - r)
case (n, r) => c(n - 1, r - 1) + c(n - 1, r)
}
Run Code Online (Sandbox Code Playgroud)
这是子集求和问题:
// is there a subset of s which has sum = t
def isSubsetSumAchievable(s: Vector[Int], t: Int) = {
// f is (i, j) => Boolean i.e. can the first i elements of s add up to j
lazy val f: ((Int, Int)) => Boolean = memoize {
case (_, 0) => true // 0 can always be achieved using empty list
case (0, _) => false // we can never achieve non-zero if we have empty list
case (i, j) =>
val k = i - 1 // try the kth element
f(k, j - s(k)) || f(k, j)
}
f(s.length, t)
}
Run Code Online (Sandbox Code Playgroud)
编辑:如下所述,这是一个线程安全的版本
def memoize[I, O](f: I => O): I => O = new mutable.HashMap[I, O]() {self =>
override def apply(key: I) = self.synchronized(getOrElseUpdate(key, f(key)))
}
Run Code Online (Sandbox Code Playgroud)
mis*_*tor 19
类/特征级别val编译为方法和私有变量的组合.因此允许递归定义.
val另一方面,本地s只是常规变量,因此不允许递归定义.
顺便说一句,即使def你定义的工作,它也不会做你期望的.在创建foo新函数对象的每次调用时,fib它将具有自己的支持映射.你应该做的是这个(如果你真的想要def成为你的公共接口):
private val fib: Memo[Int, BigInt] = Memo {
case 0 => 0
case 1 => 1
case n => fib(n-1) + fib(n-2)
}
def foo(n: Int) = {
fib(n)
}
Run Code Online (Sandbox Code Playgroud)
Scalaz有一个解决方案,为什么不重用它?
import scalaz.Memo
lazy val fib: Int => BigInt = Memo.mutableHashMapMemo {
case 0 => 0
case 1 => 1
case n => fib(n-2) + fib(n-1)
}
Run Code Online (Sandbox Code Playgroud)
您可以在Scalaz中阅读有关memoization的更多信息.