57

我想记住这一点:

def fib(n: Int) = if(n <= 1) 1 else fib(n-1) + fib(n-2)
println(fib(100)) // times out

所以我写了这个,这令人惊讶地编译和工作(我很惊讶,因为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

但是当我尝试在 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)
} 

以上编译失败error: forward reference extends over definition of value fib case n => fib(n-1) + fib(n-2)

为什么声明val fib内部 def 失败但在类/对象范围外部有效?

澄清一下,为什么我可能想在 def 范围内声明递归记忆函数 - 这是我对子集和问题的解决方案:

/**
   * 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)
  }
4

5 回答 5

47

我找到了一种使用 Scala 进行记忆的更好方法:

def memoize[I, O](f: I => O): I => O = new mutable.HashMap[I, O]() {
  override def apply(key: I) = getOrElseUpdate(key, f(key))
}

现在你可以写斐波那契如下:

lazy val fib: Int => BigInt = memoize {
  case 0 => 0
  case 1 => 1
  case n => fib(n-1) + fib(n-2)
}

这是一个有多个参数的(选择函数):

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

这是子集和问题:

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

编辑:如下所述,这是一个线程安全的版本

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)))
}
于 2016-04-30T21:03:09.843 回答
20

类/特征级别val编译为方法和私有变量的组合。因此允许递归定义。

val另一方面,Local s 只是常规变量,因此不允许递归定义。

顺便说一句,即使def您定义的有效,它也不会达到您的预期。每次调用foo新函数对象fib时,都会创建一个新的函数对象,并且它会有自己的支持映射。相反,您应该做的是(如果您真的希望 adef成为您的公共界面):

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)
} 
于 2013-04-27T22:45:48.633 回答
11

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

您可以在 Scalaz中阅读有关记忆的更多信息。

于 2016-09-10T11:19:42.603 回答
1

可变 HashMap 不是线程安全的。同样为基本条件单独定义 case 语句似乎是不必要的特殊处理,而 Map 可以加载初始值并传递给 Memoizer。以下是 Memoizer 的签名,它接受一个备忘录(不可变映射)和公式并返回一个递归函数。

Memoizer 看起来像

def memoize[I,O](memo: Map[I, O], formula: (I => O, I) => O): I => O

现在给出以下斐波那契公式,

def fib(f: Int => Int, n: Int) = f(n-1) + f(n-2)

带有 Memoizer 的斐波那契可以定义为

val fibonacci = memoize( Map(0 -> 0, 1 -> 1), fib)

其中上下文不可知的通用 Memoizer 被定义为

    def memoize[I, O](map: Map[I, O], formula: (I => O, I) => O): I => O = {
        var memo = map
        def recur(n: I): O = {
          if( memo contains n) {
            memo(n) 
          } else {
            val result = formula(recur, n)
            memo += (n -> result)
            result
          }
        }
        recur
      }

同样,对于阶乘,公式为

def fac(f: Int => Int, n: Int): Int = n * f(n-1)

Memoizer 的阶乘是

val factorial = memoize( Map(0 -> 1, 1 -> 1), fac)

灵感:Memoization,Douglas Crockford 的 Javascript 优秀部分的第 4 章

于 2017-08-26T21:47:39.113 回答
-1

ZIO#cached 是一种在 ZIO 中进行记忆的方法

于 2020-06-19T14:35:38.963 回答