📜  R 编程中的抽象

📅  最后修改于: 2022-05-13 01:55:35.561000             🧑  作者: Mango

R 编程中的抽象

使用 R 语言一段时间的人可能已经熟悉将特性作为参数传递给其他函数。但是,人们从他们的个人自定义代码中返回功能的可能性要小得多。这简直太可怕了,因为这样做可以开辟一个全新的抽象国际,这可能会大大降低对完成某些类型的职责至关重要的代码的数量和复杂性。在这里,我们提供了一些简短的示例,说明 R 程序员可以使用词法闭包来封装记录和策略的方式。

R 编程中的抽象

在 R 中的实现

首先,举一个简单的例子,假设您想要一个函数,为其参数提供add_2() 。你大概可以这样写:

R
add_2 <- function(y) { 2 + y }


R
add_x <- function(x) {
   function(y) { x + y }
}


R
add_2 <- add_x(2)
add_7 <- add_x(7)


R
for (i in 1:nboot) {
 bootmeans[i] <- mean(sample(data, length(data), 
                             replace = TRUE))
}


R
make_container <- function(n) {
   x <- numeric(n)
   i <- 1
  
   function(value = NULL) {
       if (is.null(value)) {
           return(x)
       }
       else {
           x[i] <<- value
           i <<- i + 1
       }  
   }
}


R
bootmeans <- make_container(nboot)
  
for (i in 1:nboot)
bootmeans(mean(sample(data, length(data), 
                      replace = TRUE)))




这正是您所期望的:

> add_2(1:10)
[1] 3 4 5 6 7 8 9 10 11 12

现在假设您需要所有其他特性,而不是为其参数提供 7。要做的草本问题可能是写下任何其他特征,就像add_2一样,其中 2 被 7 替换。但这将是非常低效的:如果将来你发现你犯了错误,而且你真的犯了错误想要乘以这些值而不是添加它们,您将被迫在某些地方交换代码。在这个微不足道的例子中,这不会很麻烦,但对于更复杂的项目,复制代码是灾难的秘诀。一个更高的概念可以是编写一个特征,它接受一个参数 x,该特征返回提供其参数 y 给 x 的所有其他函数。换句话说,是这样的:

电阻

add_x <- function(x) {
   function(y) { x + y }
}

现在,当您使用参数命名add_x时,您可能会返回一个完全满足您需要的功能:

电阻

add_2 <- add_x(2)
add_7 <- add_x(7)
> add_2(1:10)
[1] 3 4 5 6 7 8 9 10 11 12
> add_7(1:10)
[1] 8 9 10 11 12 13 14 15 16 17

所以这看起来并不太惊天动地。但是,如果您仔细查看add_x 的定义,您可能会注意到一些奇怪的事情:返回特性如何实现在稍后引用 x 时发现 x 的位置?

事实证明,R 是词法作用域的,这意味着特性提供了与它们被描述的环境的联系。在这种情况下,当您调用add_x时,您提供的 x 参数会附加到返回特性的环境中。在不同的短语中,在这个简单的实例中,您可能会将 R 视为简单地将特征中 x 变量的所有实例更改为降低到您指定的值,而您称为add_x 。好的,所以这可能是一个巧妙的技巧,但是,如何更有效地使用它?对于稍微复杂的实例,认为您正在执行一些复杂的引导,并且为了效率,您预先分配容器向量以保留结果。如果您只有一个效果向量,这很容易——您需要做的就是在将最终结果上传到向量时考虑迭代索引计数器。



电阻

for (i in 1:nboot) {
 bootmeans[i] <- mean(sample(data, length(data), 
                             replace = TRUE))
}
> mean(data)
[1] 0.0196
> mean(bootmeans)
[1] 0.0188

但是认为您需要跟踪几个非同寻常的统计数据,每个统计数据都要求您保持对唯一索引变量的跟踪。如果您的 bootstrapping 普通程序甚至有点复杂,这可能会很乏味并且容易出错。通过使用闭包,您可以汇总所有这些簿记。这是一个包装预先分配的容器向量的构造函数:

电阻

make_container <- function(n) {
   x <- numeric(n)
   i <- 1
  
   function(value = NULL) {
       if (is.null(value)) {
           return(x)
       }
       else {
           x[i] <<- value
           i <<- i + 1
       }  
   }
}

当您使用问题调用make_container时,它会预先分配一个指定时间段 n 的数字向量,并返回一个特征,该特征允许您对该向量进行特征统计,而不必担心保持索引的音乐性。如果您不认为返回特征的参数为 NULL,则整个向量是下背部。

电阻

bootmeans <- make_container(nboot)
  
for (i in 1:nboot)
bootmeans(mean(sample(data, length(data), 
                      replace = TRUE)))
> mean(data)
[1] 0.0196
> mean(bootmeans())
[1] 0.0207

这里make_container非常简单,但也可能像您需要的一样复杂。例如,您可能希望构造函数执行一些昂贵的计算,而您不能在每个已知字符的情况下都进行这些计算。实际上,这就是我什至在boolean3包中完成的处理,以减少在优化习惯的每个新版本中执行的计算范围。