【问题标题】:Can a convolution function written in tail recursive form?可以用尾递归形式编写卷积函数吗?
【发布时间】:2017-05-25 21:49:24
【问题描述】:

我有一个想要以尾递归形式编写的函数。该函数通过滚动s 双面骰子n 次来计算获得k 总和的方法数。我在this answer 上看到了这个函数的数学解。如下:

我在 R 中的参考递归实现是:

sum_ways <- function(n_times, k_sum, s_side) {
  if (k_sum < n_times || k_sum > n_times * s_side) {
    return(0)
  } else if (n_times == 1) {
    return(1)
  } else {
    sigma_values <- sapply(
      1:s_side, 
      function(j) sum_ways(n_times - 1, k_sum - j, s_side)
    )
    return(sum(sigma_values))
  }
}

我已经尝试按照从this answer 学到的继续传递风格重新编写函数,但我没有成功。有没有办法以尾递归的形式编写这个函数?

编辑

我知道 R 没有针对尾递归进行优化。我的问题不是 R 特定的,任何其他语言的解决方案同样受欢迎。即使它是一种没有针对尾递归进行优化的语言。

【问题讨论】:

  • ?Recall
  • @42- 我很高兴知道这一点,谢谢,但我看不出它有什么帮助,因为我没有更改函数名称的复杂性跨度>
  • 递归在这种情况下不会是一个非常有效的实现,使用动态编程/记忆化来存储f的值已经计算和重用。
  • @sandipan 当我需要这个函数时,我编写了我的参考实现并将它与 memoization 一起使用。它工作得很好。在这一点上,我不是在改进实现,我只是想知道尾递归是否适用于这种情况。

标签: recursion functional-programming dynamic-programming tail-recursion continuation-passing


【解决方案1】:

sapply 不是延续传递风格,所以你必须替换它。

以下是 Python 中延续传递风格的翻译(另一种 没有 有适当尾调用的语言):

def sum_ways_cps(n_times, k_sum, s_side, ctn):
    """Compute the number of ways to get the sum k by rolling an s-sided die
    n times. Then pass the answer to ctn."""

    if k_sum < n_times or k_sum > n_times * s_side:
        return ctn(0)
    elif n_times == 1:
        return ctn(1)
    else:
        f = lambda j, ctn: sum_ways_cps(n_times - 1, k_sum - j, s_side, ctn)
        return sum_cps(1, s_side + 1, 0, f, ctn)

def sum_cps(j, j_max, total_so_far, f, ctn):
    """Compute the sum of f(x) for x=j to j_max.
    Then pass the answer to ctn."""

    if j > j_max:
        return ctn(total_so_far)
    else:
        return f(j, lambda result: sum_cps(j + 1, j_max, total_so_far + result, f, ctn))


sum_ways_cps(2, 7, 6, print)  # 6

【讨论】:

  • 哇,我读到了stackoverflow。严格来说,sum_ways_cps 会被认为是尾递归吗?
  • 这里的递归是sum_ways_cps尾部调用sum_cps,尾部调用f,尾部调用sum_ways_cps
  • 这可以写成一个函数吗?
  • @ErenTantekin 主要障碍是表示算法的状态。原始算法包含非尾递归调用,每当其中一个发生时,调用者的局部变量(表示“剩余待完成的工作”)都存储在堆栈中。此答案中的版本使用延续链(lambdas)来表示相同的信息。如果您想避免使用 lambda,则必须以其他方式表示信息(可能使用 cons 之类的东西)。
【解决方案2】:

试试这个(使用递归,如果我们想要尾递归版本,我们需要考虑线性递归关系):

f <- function(n, k) {
  if (n == 1) {                 # base case
    return(ifelse(k<=6, 1, 0))
  } else if (k > n*6 | k < n) { # some validation
    return(0)
  } 
  else {
    # recursive calls, f(1,j)=1, 1<=j<=6, otherwise 0  
    return(sum(sapply(1:min(k-n+1, 6), function(j) f(n-1,k-j))))  
  }
}

sapply(1:13, function(k) f(2, k))
# [1] 0 1 2 3 4 5 6 5 4 3 2 1 0

【讨论】:

  • 这和我的实现差不多。我认为它不符合尾递归的条件,因为最终操作不是对自身的调用,而是对自身的多次调用,而不是求和。
  • @refik 因为原来的递归关系是卷积形式(这涉及到f 的多次调用的总和),所以精确的递归实现也将是相同的类型。如果我们可以将递归关系表示为f(n,k)=f(n-p,r)+g(r),对于一些整数p, r和一些函数g,那么它可以写成一个尾递归函数。
  • 这不是回答:“不,它不能这样做,因为......”而不是“试试这个......”?
  • @refik 不确定,可能有一些我们不知道的不错的递归公式。我们需要思考。我只是想提出一些递归的解决方案,有点接近您的要求。
猜你喜欢
  • 2013-05-05
  • 1970-01-01
  • 2012-12-30
  • 2023-03-25
  • 2017-05-23
  • 2020-09-16
  • 1970-01-01
  • 2012-03-04
相关资源
最近更新 更多