在获得基本想法之后,以命令式方式编写动态编程(DP)问题非常简单,至少对于更简单的DP问题。它通常涉及某种形式的表格,我们根据某些公式迭代填充。这几乎就是实现自下而上的DP。
让我们将Longest Increasing Subsequence(LIS)作为一个简单而常见的问题示例,可以通过自下而上的DP算法解决。
C ++实现很简单(未经测试):
// Where A is input (std::vector<int> of size n)
std::vector<int> DP(n, 0);
for(int i = 0; i < n; i++) {
DP[i] = 1;
for(int j = 0; j < i; j++) {
if (A[i] > A[j]) {
DP[i] = std::max(DP[i], DP[j] + 1);
}
}
}
我们刚刚描述了“如何填充DP”,这正是命令式编程所在。
如果我们决定描述“DP是什么”,这是一种更加考虑它的FP方式,它会变得有点复杂。 以下是此问题的示例Scala代码:
// Where A is input
val DP = A.zipWithIndex.foldLeft(Seq[Int]()) {
case (_, (_, 0)) => Seq(1)
case (d, (a, _)) =>
d :+ d.zipWithIndex.map { case (dj, j) =>
dj + (if (a > A(j)) 1 else 0)
}.max
}
说实话,这个Scala实现似乎并不那么惯用。它只是命令式解决方案的翻译,增加了不变性。
我很好奇,处理这类事情的一般FP方法是什么?
答案 0 :(得分:1)
我对DP知之甚少,但我希望有一些观察结果可以促成对话。
首先,您的示例Scala代码似乎不能解决LIS问题。我插入了Wikipedia page上找到的Van der Corput序列,但没有得到指定的结果。
解决问题这是我提出的解决方案。
def lis(a: Seq[Int], acc: Seq[Int] = Seq.empty[Int]): Int =
if (a.isEmpty) acc.length
else if (acc.isEmpty) lis(a.tail, Seq(a.head)) max lis(a.tail)
else if (a.head > acc.last) lis(a.tail, acc :+ a.head) max lis(a.tail, acc)
else lis(a.tail, acc)
lis(Seq(0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15)) // res0: Int = 6
这可以调整为返回子序列本身,并且我确定可以调整它以获得更好的性能。
至于备忘录,根据需要自行推出并不难。这是记忆任何arity-2函数的基本概要。
def memo[A,B,R](f: (A,B)=>R): ((A,B)=>R) = {
val cache = new collection.mutable.WeakHashMap[(A,B),R]
(a:A,b:B) => cache.getOrElseUpdate((a,b),f(a,b))
}
有了这个,我可以创建一个通常称为方法/功能的备忘版本。
val myfunc = memo{(s:String, i:Int) => s.length > i}
myfunc("bsxxlakjs",7) // res0: Boolean = true
注意:以前建议使用WeakHashMap
,以便缓存可以在内存受到挑战的环境中删除较少使用的元素。我不知道是否仍然如此。
答案 1 :(得分:1)