scala dynamic-programming memoization

Memoización de Scala: ¿Cómo funciona esta nota Scala?



dynamic-programming memoization (1)

Soy el autor del código anterior .

/** * 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] }

En la Memo[I <% K, K, O] :

I: input K: key to lookup in cache O: output

La línea I <% K significa que K puede ser viewable (es decir, convertido implícitamente) desde I

En la mayoría de los casos, debería ser K por ejemplo, si está escribiendo fibonacci que es una función de tipo Int => Int , está bien almacenar en caché el mismo Int .

Pero, a veces, cuando escribe memoización, no siempre desea memorizar o almacenar en caché mediante la entrada en sí ( I ), sino más bien como una función de la entrada ( K ), por ejemplo, cuando escribe el algoritmo de subsetSum que tiene entrada de tipo (List[Int], Int) , no desea usar List[Int] como la clave en su caché, sino que desea usar List[Int].size como la parte de la clave en su caché.

Entonces, aquí hay un caso concreto:

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

Por supuesto, puede acortar todo esto en una sola línea: type DP = Memo[(List[Int], Int), (Int, Int), Boolean]

Para el caso común (cuando I = K ), simplemente puede hacer esto: type ==>[I, O] = Memo[I, I, O] y utilícelo de esta manera para calcular la capa binomial con memoria recursiva:

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

Para ver detalles de cómo funciona la sintaxis anterior, consulte esta pregunta .

Aquí hay un ejemplo completo que calcula editDistance codificando ambos parámetros de la entrada (Seq, Seq) a (Seq.length, Seq.length) :

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

Y por último, el ejemplo de la fibonacci canónica:

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

El siguiente código es del repositorio de programación dinámica de Pathikrit . Estoy desconcertado por su belleza y peculiaridad.

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

El tipo Memo se implementa en otro archivo:

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

Mis preguntas son:

  1. ¿Por qué se declara el type K como (Int, Int) en subsetSum?

  2. ¿Qué significa int en (Int, Int) respectivamente?

3. ¿Cómo se convierte (List[Int], Int) implícitamente a (Int, Int) ?
No veo una implicit def foo(x:(List[Int],Int)) = (x._1.toInt,x._2) . (Ni siquiera en el archivo Implicits.scala que importa.

* Edit: Bueno, extraño esto:

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

Disfruto mucho de la biblioteca de scalgos . Hay muchas perlas de Scala en ella. Por favor, ayúdame con esto para que pueda apreciar el ingenio de Pathikrit. Gracias. (: