【问题标题】:Solving dynamic programming problems using functional programming使用函数式编程解决动态规划问题
【发布时间】:2017-12-29 07:36:02
【问题描述】:

了解基本概念后,以命令式风格编写动态编程 (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 方式是什么?

【问题讨论】:

  • 我不了解 scala,但是很多函数式编程库都有一个 memoize 模式,它以透明的方式进行缓存。
  • 是的,感谢您向我指出那个方向,将查找以了解更多信息。不过,乍一看,它似乎更适用于自上而下的 DP 算法?谢谢!
  • 谢谢,但我正在寻找更通用的方法。
  • 基本思想是将DP表示为一个流,并声明一个生成它的递归函数。您可以从生成斐波那契数 stackoverflow.com/questions/9864497/… 的更简单的问题中获得启发。
  • 很抱歉,但我认为这与 DP 无关。我认为您提供的链接上的解决方案根本没有任何记忆。

标签: scala functional-programming dynamic-programming purely-functional


【解决方案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,以便缓存可以在内存挑战的环境中丢弃较少使用的元素。不知道是不是还是这样。

【讨论】:

    【解决方案2】:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-02-25
      • 1970-01-01
      • 2020-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-25
      相关资源
      最近更新 更多