【问题标题】:Convert simple recursive relation to functional program将简单的递归关系转换为函数式程序
【发布时间】:2013-03-20 21:12:11
【问题描述】:

您好,我正在尝试将以下递归伪代码定义转换为 R 中的函数式编程构造:

a = [ random numbers between 0 and 10 ]
b = [ random numbers between -5 and 5 ]
c = [ random numbers between 0 and -10 ]

x0 = 200
index = 0

function f(x) {
    index = index + 1
    if( x is between 200 and 220 ) return f(x + a[index])
    else if(x is between 220 and 250) return f(x + b[index])
    else if(x is 250 and above) return f(x + c[index])
}

可执行的R代码是:

a <- sample(1:10,size=50, replace=TRUE)
b <- sample(-5:5,size=50, replace=TRUE)
c <- sample(-1:-10,size=50, replace=TRUE)

index <- 0;


myfunc <- function(x){
  index <<- index + 1;
  if(index == 50) return(x)
  if(x <= 220){ return(myfunc(x + a[index])) }
  else if(x > 220 & x < 250){ return(myfunc(x + b[index])) }
  else {return( myfunc(x + c[index]))}
}
print(myfunc(200));

想讨论任何方法,包括 Map/Filter/Reduce 或 Vectorisation。非常感谢。

此外,我怎样才能保留 50 个 x 元素的整个路径(而不是只看 x 的一个答案)。

【问题讨论】:

  • iiuc,你说的这些功能在这里都没用……
  • 说实话,你的代码看起来有点奇怪,不清楚你想要实现什么。你能用统计术语解释你想模拟什么随机变量(或向量)吗? “50 x 元素的整个路径”是什么意思?
  • 大家好,感谢您的关注。它是一个从 200 开始的路径相关值。然后它在 200 到 300 之间移动 50 次,具体取决于 x 的前一个值(因此是递归定义)。通过 50 个 x 元素的整个路径意味着我想通过函数 myfunc 知道 x 的所有中间值。例如,如果我使用 for 循环重写它,我将声明一个结果向量为 50,然后在到达最终值 x 之前迭代地填充它。你要我写一个for循环的版本吗?
  • 你的函数是如何递归的?为什么不想写一个 for 循环呢?
  • 嗨 @hadley 是的 - 大多数递归都可以表示为 for 循环,我可以在这里轻松地创建一个 for 循环。但我试图在函数式编程结构中表达这一点。关于在应用场景中理解 R 函数式编程的力量的文档/指南非常少。

标签: r functional-programming


【解决方案1】:

您可以使用带有累积选项的 Reduce 函数来保存所有中间值。

要了解它是如何工作的,请在简单的“求和”函数上尝试一下

x = rep(200, 50)
Reduce(x=x, f=sum) 
Reduce(x=x, f=sum, accumulate=T)

您正在寻找的答案需要您重写您的特殊函数,以便将其传递给 Reduce:

foo <- function(x, y = 0){
    if (200 <= x & x < 220){
        x + sample(1:10, 1)
    }
    else if(220 <= x & x < 250){
        x + sample(-5:5, 1)
    }
    else if (250 <= x){
        x + sample(-1:-10, 1)
    }
}

Reduce(x=rep(200, 50), f=foo, accumulate=T)

【讨论】:

  • 谢谢,太好了。正是我正在寻找的那种洞察力。如果我无法使用 sample 并且必须从列表中实际查找,那该怎么办?另外我不明白你为什么将 x 初始化为 200s ?
  • 如果用 200,0,0,0, 的向量初始化,它也能正常工作...这只是第一个重要的元素。
  • 抓住了 - 我将如何进行查找?会声明一个全局变量并跟踪索引工作吗?
  • 是的,使用全局变量有效。您只需要在每次调用时从 foo 内部增加全局索引,就像您在问题中所写的那样。您还必须记住在调用 Reduce 之前将 index 重置为 0。就个人而言,我认为循环比“函数式”方法更容易理解。
  • Reduce 一次将函数应用于向量的两个部分,1. 先前计算的部分,2. 当前元素。应用于向量 [a,b,c,d,e] 的函数 f 返回 f(f(f(f(a, initial),b),c),d),e)。我使用默认参数创建了该函数,因此它知道在第一个元素处做什么。
【解决方案2】:

我还没有把它变成函数形式,但我可以在 R 中做我认为你想做的事情:

 x=200; index=0; while (index < 50) {index <- index + 1;
    if(tail(x,1) <= 220){ x <-c(x,  tail(x,1)+a[index]) } else
     { if(tail(x,1)  & tail(x,1)  < 250) { x <-c(x , tail(x,1)+b[index]) }  else
        {x <-c( x , tail(x,1)+c[index])} }
  }
 x
 [1] 200 204 206 210 213 215 216 219 227 222 219 220 223 221 224 229 231 227 226 229 224 223 221 221
[25] 216 218 223 220 226 221 217 224 228 228 231 236 233 234 229 227 230 229 227 227 225 225 228 232
[49] 227 230 228

也许这将有助于找到一种“功能化”它的方法。我认为Reducereplicate 或者参考类对象将很有可能提供机制。这只是将 x 向量扩展 1 个元素,然后在下一次迭代中使用该元素来选择要使用的增量向量。如果你想超过 50 长度的输出,你可以使用模余数作为索引。

【讨论】:

  • 谢谢@DWin 我有一种偷偷摸摸的感觉你可能会回答这个问题:)。我想我的问题普遍存在于函数式编程中,以解决更棘手的现实世界问题——尤其是对于一生都在做 OO / 程序性工作的人。
  • 我承认这种模拟也有困难。 “通常”的索引方法都不能很好地工作。 cumsumcumprod 应该有递归/增量版本。如果我是 C++ 程序员,我会使用 Rcpp 包。
【解决方案3】:

首先,我将首先对函数进行参数化以匹配您的原始描述,并在模拟参数发生变化时使事情更容易更改。

foo_cat <- function(x) {
  if (200 <= x & x < 220) return("a")
  if (220 <= x & x < 250) return("b")
  if (250 <= x) return("c")

  stop("x out of range")
}
ranges <- list(a = 1:10, b = -5:5, c = -(1:10))

foo_sample <- function(x, n = 1) {
  sample(ranges[[foo_cat(x)]], n, rep = TRUE)
}

对我来说,这是函数式编程中最重要的部分:编写封装解决方案重要组件的函数。

接下来我们将使用foo_sample 来通过for 循环解决问题。这使得当前值和先前值之间的关系变得明确:

n <- 50
out <- c(200, rep(NA, n - 1))

for(i in seq(2, n)) {
  out[i] <- out[i - 1] + foo_sample(out[i - 1])
}

接下来,您可以考虑删除 for 循环并将其替换为 functional。不幸的是,没有封装此模式的内置函数,因此您可以编写自己的(如果这是代码中非常常见的模式,这是一个好主意),或者坚持使用 for 循环(如果您愿意,这是个好主意您的代码易于阅读)。

【讨论】:

  • 这看起来很有趣。让我测试一下。谢谢!!
猜你喜欢
  • 2023-04-05
  • 2021-12-09
  • 1970-01-01
  • 1970-01-01
  • 2016-01-06
  • 1970-01-01
  • 1970-01-01
  • 2013-03-02
  • 1970-01-01
相关资源
最近更新 更多