The following code is from Pathikrit's Dynamic Programming repository. I'm mystified by both its beauty and peculiarity.
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)
}
The type Memo
is implemented in another file:
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))
}
My questions are:
Why is
type K
declared as(Int, Int)
in subsetSum?What does the
int
in(Int, Int)
stand for respectively?
3. How does (List[Int], Int)
implicitly convert to (Int, Int)
?
I see no implicit def foo(x:(List[Int],Int)) = (x._1.toInt,x._2)
. ( not even in the Implicits.scala
file it imports.
*Edit: Well, I miss this:
implicit def encode(key: (List[Int], Int)) = (key._1.length, key._2)
I enjoy Pathikrit's library scalgos very much. There are a lot of Scala pearls in it. Please help me with this so I can appreciate Pathikrit's wit. Thank you. (:
I am the author of the above code.
In
Memo[I <% K, K, O]
:The line
I <% K
means theK
can be viewable (i.e. implicitly converted) fromI
.In most cases,
I
should beK
e.g. if you are writingfibonacci
which is a function of typeInt => Int
, it is okay to cache byInt
itself.But, sometimes when you are writing memoization, you do not want to always memoize or cache by the input itself (
I
) but rather a function of the input (K
) e.g when you are writing thesubsetSum
algorithm which has input of type(List[Int], Int)
, you do not want to useList[Int]
as the key in your cache but rather you want useList[Int].size
as the part of the key in your cache.So, here's a concrete case:
You can ofcourse shorten all these into a single line:
type DP = Memo[(List[Int], Int), (Int, Int), Boolean]
For the common case (when
I = K
), you can simply do this:type ==>[I, O] = Memo[I, I, O]
and use it like this to calculate the binomial coeff with recursive memoization:To see details how above syntax works, please refer to this question.
Here is a full example which calculates editDistance by encoding both the parameters of the input
(Seq, Seq)
to(Seq.length, Seq.length)
:And lastly, the canonical fibonacci example: