web-dev-qa-db-ja.com

Scalaメモ化:これはどのように機能しますかScalaメモは機能しますか?

次のコードは Pathikritの動的計画法 リポジトリからのものです。私はその美しさと特異性の両方に不思議に思っています。

_def subsetSum(s: List[Int], t: Int) = {
  type DP = Memo[(List[Int], Int), (Int, Int), Seq[Seq[Int]]]
  implicit def encode(key: (List[Int], Int)) = (key._1.length, key._2)

  lazy val f: DP = Memo {
    case (Nil, 0) => Seq(Nil)
    case (Nil, _) => Nil
    case (a :: as, x) => (f(as, x - a) map {_ :+ a}) ++ f(as, x)
  }

  f(s, t)
}
_

タイプMemoは別のファイルに実装されています。

_case class Memo[I <% K, K, O](f: I => O) extends (I => O) {
  import collection.mutable.{Map => Dict}
  val cache = Dict.empty[K, O]
  override def apply(x: I) = cache getOrElseUpdate (x, f(x))
}
_

私の質問は次のとおりです。

  1. _type K_がsubsetSumで_(Int, Int)_として宣言されているのはなぜですか?

  2. _(Int, Int)_のintはそれぞれ何を表していますか?

3. _(List[Int], Int)_はどのようにして暗黙的に_(Int, Int)_に変換されますか?
implicit def foo(x:(List[Int],Int)) = (x._1.toInt,x._2)が表示されません。 (インポートする_Implicits.scala_ファイルでもありません。

*編集:まあ、私はこれが恋しいです:

_implicit def encode(key: (List[Int], Int)) = (key._1.length, key._2)
_

私はPathikritのライブラリを楽しんでいます scalgos とても。たくさんのScala真珠が入っています。Pathikritの機知に感謝できるように、これを手伝ってください。ありがとうございます。(:

24
Yun-Chih Chen

私は 上記のコード の作者です。

_/**
 * Generic way to create memoized functions (even recursive and multiple-arg ones)
 *
 * @param f the function to memoize
 * @tparam I input to f
 * @tparam K the keys we should use in cache instead of I
 * @tparam O output of f
 */
case class Memo[I <% K, K, O](f: I => O) extends (I => O) {
  import collection.mutable.{Map => Dict}
  type Input = I
  type Key = K
  type Output = O
  val cache = Dict.empty[K, O]
  override def apply(x: I) = cache getOrElseUpdate (x, f(x))
}

object Memo {
  /**
   * Type of a simple memoized function e.g. when I = K
   */
  type ==>[I, O] = Memo[I, I, O]
}
_

_Memo[I <% K, K, O]_:

_I: input
K: key to lookup in cache
O: output
_

行_I <% K_は、KIから 表示可能 (つまり暗黙的に変換)できることを意味します。

ほとんどの場合、IKである必要があります。例: _Int => Int_型の関数であるfibonacciを記述している場合は、Int自体でキャッシュしても問題ありません。

ただし、メモ化を作成しているときは、入力自体(I)ではなく、入力の関数(K)で常にメモ化またはキャッシュしたい場合があります。たとえば、作成中などです。タイプ_(List[Int], Int)_の入力を持つsubsetSumアルゴリズムでは、キャッシュ内のキーとして_List[Int]_を使用するのではなく、_List[Int].size_をの一部として使用する必要があります。キャッシュ内のキー。

それで、ここに具体的なケースがあります:

_/**
 * Subset sum algorithm - can we achieve sum t using elements from s?
 * O(s.map(abs).sum * s.length)
 *
 * @param s set of integers
 * @param t target
 * @return true iff there exists a subset of s that sums to t
 */
 def isSubsetSumAchievable(s: List[Int], t: Int): Boolean = {
    type I = (List[Int], Int)     // input type
    type K = (Int, Int)           // cache key i.e. (list.size, int)
    type O = Boolean              // output type      

    type DP = Memo[I, K, O]

    // encode the input as a key in the cache i.e. make K implicitly convertible from I
    implicit def encode(input: DP#Input): DP#Key = (input._1.length, input._2)   

    lazy val f: DP = Memo {
      case (Nil, x) => x == 0      // an empty sequence can only achieve a sum of zero
      case (a :: as, x) => f(as, x - a) || f(as, x)      // try with/without a.head
    }

    f(s, t)
 }
_

もちろん、これらすべてを1行に短縮することもできます:type DP = Memo[(List[Int], Int), (Int, Int), Boolean]

一般的なケース(_I = K_の場合)の場合、次のように簡単に実行できます:_type ==>[I, O] = Memo[I, I, O]_そして次のように使用します 二項係数を計算します 再帰的なメモ化:

_  /**
   * http://mathworld.wolfram.com/Combination.html
   * @return memoized function to calculate C(n,r)
   */
  val c: (Int, Int) ==> BigInt = Memo {
    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)
  }
_

上記の構文がどのように機能するかについての詳細を確認するには、 この質問を参照してください

これは、入力_(Seq, Seq)_から_(Seq.length, Seq.length)_の両方のパラメーターをエンコードすることによって editDistance を計算する完全な例です。

_ /**
   * Calculate edit distance between 2 sequences
   * O(s1.length * s2.length)
   *
   * @return Minimum cost to convert s1 into s2 using delete, insert and replace operations
   */
  def editDistance[A](s1: Seq[A], s2: Seq[A]) = {

    type DP = Memo[(Seq[A], Seq[A]), (Int, Int), Int]
    implicit def encode(key: DP#Input): DP#Key = (key._1.length, key._2.length)

    lazy val f: DP = Memo {
      case (a, Nil) => a.length
      case (Nil, b) => b.length
      case (a :: as, b :: bs) if a == b => f(as, bs)
      case (a, b) => 1 + (f(a, b.tail) min f(a.tail, b) min f(a.tail, b.tail))
    }

    f(s1, s2)
  }
_

そして最後に、標準的なフィボナッチの例:

_lazy val fib: Int ==> BigInt = Memo {
  case 0 => 0
  case 1 => 1
  case n if n > 1 => fib(n-1) + fib(n-2)
}

println(fib(100))
_
56
pathikrit