在R中缓存Vector的均值

kha*_*han 4 caching r mean

我正在学习R并且在实践作业中遇到了一些代码.

 makeVector <- function(x = numeric()) {
         m <- NULL
         set <- function(y) {
                x <<- y
                m <<- NULL
        }
        get <- function() x
        setmean <- function(mean) m <<- mean
        getmean <- function() m
        list(set = set, get = get,
             setmean = setmean,
             getmean = getmean)
 }
Run Code Online (Sandbox Code Playgroud)

文件说:

该函数makeVector创建一个特殊的"向量",它实际上是一个包含函数的列表

  1. 设置向量的值
  2. 获取向量的值
  3. 设置均值的值
  4. 得到平均值

但我无法理解该函数是如何工作的,除非它在该特定环境中为变量m赋值均值.

Eva*_*sky 13

m <- NULL 首先将均值设置为NULL作为未来值的占位符

set <- function(y) {x <<- y; m <<- NULL}定义一个函数来设置向量,x到一个新的向量y,并重置平均值m,到NULL

get <- function() x 返回向量, x

setmean <- function(mean) m <<- mean设置均值m,到mean

getmean <- function() m 返回均值, m

list(set = set, get = get,setmean = setmean,getmean = getmean) 返回包含刚刚定义的所有函数的"特殊向量"


Len*_*ski 6

此答案摘自我最初于 2016 年作为约翰霍普金斯大学R 编程课程的社区导师撰写的文章:揭秘 makeVector()

makeVector()和cachemean()的总体设计

cachemean.R 文件包含两个函数,makeVector()cachemean()。文件中的第一个函数makeVector()创建一个存储向量及其平均值的 R 对象。第二个函数cachemean()需要一个返回的参数,makeVector()以便从存储在对象环境中的缓存值中检索平均值makeVector()

makeVector() 中发生了什么?

要理解的关键概念makeVector()是它构建一组函数并将列表中的函数返回到父环境。那是,

    myVector <- makeVector(1:15)
Run Code Online (Sandbox Code Playgroud)

生成一个对象 ,myVector它包含四个函数:set()get()setmean()getmean()。它还包括两个数据对象xm

由于词法作用域,myVector包含环境的完整副本,包括在设计时(即编码时)makeVector()定义的任何对象。makeVector()环境层次结构图清楚地表明了myVector.

在此输入图像描述

以层次结构的形式说明,全局环境包含makeVector()环境。所有其他内容都存在于makeVector()环境中,如下图所示。

在此输入图像描述

由于每个函数在 R 中都有自己的环境,因此层次结构说明对象 x 和 m 是四个函数 、get()set()getmean()的同级函数setmean()

一旦函数运行并且类型的对象makeVector()被实例化(即创建),包含 myVector 的环境如下所示:

在此输入图像描述

请注意,该对象x包含向量1:15,即使myVector$set()尚未执行。出现这种情况是因为该值1:15作为参数传递到makeVector()函数中。如何解释这种行为?

当 R 函数返回一个包含其父环境函数的对象时(就像 之类的调用一样myVector <- makeVector(1:15)),它不仅myVector可以访问其列表中的特定函数,而且还保留对 定义的整个环境的访问权限makeVector(),包括用于启动函数的原始参数。

为什么会这样呢?myVector包含指向函数结束后环境中的函数的指针,因此这些指针可以防止垃圾收集器释放所makeVector()消耗的内存。makeVector()因此,整个makeVector()环境保留在内存中,并且myVector可以访问其函数以及该环境中在其函数中引用的任何数据。

此功能解释了为什么x(在原始函数调用上初始化的参数)可以通过对myVector诸如 等函数的后续调用进行访问myVector$get(),并且还解释了为什么代码无需显式发出设置myVector$set()的值即可工作x

makeVector() 一步一步

现在,让我们逐步分解该函数的行为。

第 1 步:初始化对象

函数中发生的第一件事是初始化两个对象xm

makeVector(x = numeric()) {
  m <- NULL
  ...
}
Run Code Online (Sandbox Code Playgroud)

请注意,它x被初始化为函数参数,因此函数内不需要进一步初始化。m设置为 NULL,将其初始化为 makeVector() 环境中的对象,以供函数中的后续代码使用。

此外,函数声明的形式部分将 的默认值定义x为空数值向量。使用默认值初始化向量非常重要,因为如果没有默认值,data <- x$get()则会生成以下错误消息。

 Error in x$get() : argument "x" is missing, with no default
Run Code Online (Sandbox Code Playgroud)

步骤 2:定义 makeVector() 类型对象的“行为”或函数

初始化存储关键信息的关键对象后makeVector(),代码提供了四种基本行为,这些行为对于面向对象程序中的数据元素来说是典型的。它们被称为“getter 和 settter”,更正式地称为mutator 和 accessor方法。正如人们所预料的那样,“getter”是检索(访问)对象内的数据的程序模块,而“setter”是设置(改变)对象内的数据值的程序模块。

首先makeVector()定义set()函数。大部分“魔法”都makeVector()发生在set()函数中。

set <- function(y) {
    x <<- y
    m <<- NULL
}
Run Code Online (Sandbox Code Playgroud)

set()接受一个名为 的参数y。假设该值是一个数值向量,但没有直接在函数形式中说明。就该函数而言,此参数是否被称为,或除 之外的任何对象名称set()并不重要。为什么?由于环境中已经定义了一个对象,因此使用相同的对象名称会使代码更难以理解。 yaVectorxxmakeVector()

在内部,set()我们使用<<- 赋值运算符的形式,它将运算符右侧的值分配给父环境中由运算符左侧的对象命名的对象。

set()执行时,它做了两件事:

  1. 将输入参数分配给x父环境中的对象,并且
  2. 将 NULL 值分配给m父环境中的对象。这行代码清除m先前执行的 所缓存的任何值cachemean()

因此,如果 中已经缓存了有效的平均值m,则每当重置时,对象内存中缓存的x值都会被清除,从而强制后续调用重新计算平均值,而不是从缓存中检索错误的值。mcachemean()

请注意, 中的两行代码set()与 main 函数中的前两行执行的操作完全相同:设置 的值x,并将 的值设置为 NULL m

其次,makeVector()定义向量的吸气剂x

get <- function() x
Run Code Online (Sandbox Code Playgroud)

同样,该函数利用了 R 中的词法作用域功能。由于符号x未在 中定义get(),因此 R 从 的父环境中检索它makeVector()

第三,makeVector()定义平均值的设置器m

setmean <- function(mean) m <<- mean
Run Code Online (Sandbox Code Playgroud)

由于m是在父环境中定义的,并且我们需要在setmean()完成后访问它,因此代码使用赋值运算符的形式将输入参数分配给父环境中<<-的值。m

最后,makeVector()定义mean的getter m。就像 的 getter 一样x,R 利用词法作用域来查找正确的符号m来检索其值。

getmean <- function() m
Run Code Online (Sandbox Code Playgroud)

此时,我们已经为对象中的两个数据对象定义了 getter 和 setter makeVector()

步骤 3:通过返回 list() 创建一个新对象

这是函数操作中“魔法”的另一部分makeVector()。代码的最后一部分将每个函数分配为 a 中的一个元素list(),并将其返回到父环境。

list(set = set, get = get,
     setmean = setmean,
     getmean = getmean)
Run Code Online (Sandbox Code Playgroud)

当函数结束时,它返回一个完整的类型对象makeVector(),供下游 R 代码使用。此代码的另一个重要微妙之处是列表中的每个元素都被命名。也就是说,列表中的每个元素都是使用elementName = value语法创建的,如下所示:

    list(set = set,          # gives the name 'set' to the set() function defined above
         get = get,          # gives the name 'get' to the get() function defined above
         setmean = setmean,  # gives the name 'setmean' to the setmean() function defined above
         getmean = getmean)  # gives the name 'getmean' to the getmean() function defined above
Run Code Online (Sandbox Code Playgroud)

命名列表元素使我们能够使用$ 提取运算符的形式按名称访问函数[[,而不是像 中那样使用提取运算符的形式myVector[[2]]()来获取向量的内容。

这里需要注意的是,该cachemean()函数需要一个类型为 的输入参数makeVector()。如果将一个正则向量传递给函数,如

 aResult <- cachemean(1:15)
Run Code Online (Sandbox Code Playgroud)

函数调用将失败,并显示错误,解释cachemean()无法访问$getmean()输入参数,因为$不适用于原子向量。这是准确的,因为原始向量不是列表,也不包含函数$getmean(),如下所示。

> aVector <- 1:10
> cachemean(aVector)
Error in x$getmean : $ operator is invalid for atomic vectors
Run Code Online (Sandbox Code Playgroud)

解释cachemean()

没有cachemean()makeVector()功能就不完整。为什么?按照设计,cachemean()需要从 类型的对象中填充或检索平均值makeVector()

cachemean <- function(x, ...) {
     ...
Run Code Online (Sandbox Code Playgroud)

与 一样makeVector()cachemean()以单个参数x和省略号开头,允许调用者将其他参数传递到函数中。

接下来,该函数尝试从作为参数传入的对象中检索平均值。首先,它调用getmean()输入对象上的函数。

     m <- x$getmean()
Run Code Online (Sandbox Code Playgroud)

然后它检查结果是否为NULL。由于makeVector()将缓存平均值设置为NULL每当将新向量设置到对象中时,如果此处的值不等于NULL,我们就有一个有效的缓存平均值,并且可以将其返回到父环境

     if(!is.null(m)) {
          message("getting cached data")
          return(m)
     }
Run Code Online (Sandbox Code Playgroud)

如果 的结果!is.null(m)FALSEcachemean()则从输入对象获取向量,计算 a mean(),使用setmean()输入对象上的函数设置输入对象中的平均值,然后通过打印平均值对象将平均值的值返回到父环境。

     data <- x$get()
     m <- mean(data, ...)
     x$setmean(m)
     m
Run Code Online (Sandbox Code Playgroud)

请注意,这cachemean()是执行该函数的唯一位置mean(),这就是为什么makeVector()没有cachemean().

将各个部分放在一起:函数在运行时如何工作

现在我们已经解释了每个函数的设计,下面是它们在 R 脚本中使用时如何工作的说明。

  aVector <- makeVector(1:10)
  aVector$get()               # retrieve the value of x
  aVector$getmean()           # retrieve the value of m, which should be NULL
  aVector$set(30:50)          # reset value with a new vector
  cachemean(aVector)          # notice mean calculated is mean of 30:50, not 1:10
  aVector$getmean()           # retrieve it directly, now that it has been cached
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

结论:cachemean() 的工作原理是什么?

总而言之,R 编程中的词法作用域分配利用了词法作用域,并且返回类型对象的函数list()还允许访问原始函数环境中定义的任何其他对象。在特定实例中,这意味着后续代码可以通过使用 getter 和 setter 来访问或makeVector()的值。这就是如何计算和存储输入参数的平均值(如果输入参数的类型为 )。由于 中的列表元素是用名称定义的,因此我们可以通过extract 运算符的形式访问这些函数。xmcachemean()makeVector()makeVector()$

有关解释分配如何使用 S3 对象系统功能的其他注释,请查看makeCacheMatrix() as an Object

附录 A:这项作业的重点是什么?

学生完成作业后,他们经常会询问作业的价值和目的。奥克兰大学的 Robert Gentleman 和 Ross Ihaka 撰写的一篇很好的文章解释了词法作用域在统计计算中的价值。

附录 B:cachemean.R

这是cachemean.R 的完整列表。

makeVector <- function(x = numeric()) {
     m <- NULL
     set <- function(y) {
          x <<- y
          m <<- NULL
     }
     get <- function() x
     setmean <- function(mean) m <<- mean
     getmean <- function() m
     list(set = set, get = get,
          setmean = setmean,
          getmean = getmean)
}
cachemean <- function(x, ...) {
     m <- x$getmean()
     if(!is.null(m)) {
          message("getting cached data")
          return(m)
     }
     data <- x$get()
     m <- mean(data, ...)
     x$setmean(m)
     m
}
Run Code Online (Sandbox Code Playgroud)

附录 C:常见问题

问:为什么不cachemean()返回缓存值?我的代码如下所示:

 cachemean(makeVector(1:100))
 cachemean(makeVector(1:100))
Run Code Online (Sandbox Code Playgroud)

答:以这种方式编写的代码会创建两个不同类型的对象makeVector(),因此两次调用都会cachemean()初始化每个实例的方法,而不是从单个实例中进行缓存和检索。说明上述代码如何运行的另一种方式如下。

在此输入图像描述

请注意第一个调用如何cachemean()设置缓存,第二个调用如何从中检索数据。

问:为什么set()代码中从未使用过?

A:set()包含在内,以便一旦makeVector()创建该类型的对象,就可以更改其值,而无需初始化该对象的另一个实例。第一次实例化该类型的对象时没有必要makeVector()。为什么?首先, 的值x被设置为函数参数,如 中makeVector(1:30)。然后,函数中的第一行代码设置m <- NULL,同时为 分配内存m并将其设置为NULL。当函数结束时对此对象的引用传递到父环境时, 和xm可以由各自的 get 和 set 函数访问。

下面的代码说明了 的使用set()

在此输入图像描述

Q:为什么要x设置默认值makeVector()

答:由于x是一个参数,因此唯一可以为其设置默认值的地方是在形式中。cachemean()未设置默认值时返回的错误类型,

  Error in x$get() : argument "x" is missing, with no default
Run Code Online (Sandbox Code Playgroud)

是不可取的。我们的代码应该直接处理错误情况,而不是依赖于 R 中的底层错误处理。

创建类型的对象makeVector()而不在初始化期间填充其值是完全有效的。makeVector()包含一个 setter 函数,因此可以在创建对象后设置其值。但是,在执行之前,该对象必须具有有效数据(数值向量)cachemean()

理想情况下,应包括在计算平均值之前cachemean()验证其不为空的逻辑。x默认设置启用xreturn cachemean()NaN这是一个合理的结果。

参考

  1. Chi, Yau - R-Tutor Named List Members,检索于 2016 年 7 月 20 日。
  2. Wickham, Hadley—— Advanced-R Functions,检索于 2016 年 7 月 17 日。
  3. Wickham, Hadley -- Advanced-R 范围界定问题,检索于 2016 年 7 月 17 日。


小智 5

我认为理解这个例子的一个好方法是尝试以下方法:

首先检查当你使用make_Vector函数时,你现在有四种不同的设置

> mvec <- makeVector()
> x <- 1:4
> mvec$set(x)
> mvec$get()
> [1] 1 2 3 4
> mvec$getmean()
> NULL
> mvec$setmean(3.4)
> mvec$getmean()
> 3.4
Run Code Online (Sandbox Code Playgroud)

3.4这不是正确的意思,我把这些数字然后你可以检查你可以设置你想要的任何数字.

作业的第二部分如下:

cachemean <- function(x, ...) {
        m <- x$getmean()
        if(!is.null(m)) {
                message("getting cached data")
                return(m)
        }
        data <- x$get()
        m <- mean(data, ...)
        x$setmean(m)
        m
}
Run Code Online (Sandbox Code Playgroud)

这些部分或代码检查您是否具有感兴趣的载体的平均值.如果存在,那么您不需要计算,您可以使用缓存变量.

我为平均值设置了一个错误的数字,那么你可以看到我已经设置了平均值如下:

> cachemean(mvec)
> 3.4
Run Code Online (Sandbox Code Playgroud)

您必须传递示例中使用的原始mvec列表