利用R语言词法作用域特性缓存数据

  R语言采用词法作用域(lexical scoping,或称静态作用域static scoping),关于作用域的相关说明可以参考Wiki,这里先举一个例子说明R语言词法作用域的特点。

  定义两个函数g() 、f() 以及一个变量z 如下所示:

z <- 10

g <- function(x) {
  x + z
}

f <- function(y) {
  z <- 20
  g(y + z)
}

第1行定义变量z 的值为10,而在函数f() 中,再次定义z 的值为20,此时调用f(1) :

> f(1)
[1] 31

可见结果为31。执行f(1) 时,由于函数f() 中定义了z 的值为20,故y + z 的值为21,调用g(21) 。在函数g() 中,对于第4行的x + z ,变量z 没有在函数g() 中定义,是一个自由变量(free variable),则继续在定义函数g() 的作用域中寻找,由于第1行定义z <- 10 ,此时认为在函数g() 中z 的值为10,g(21) 返回21+10 = 31,也就是f(1) 的返回值。对于使用了词法作用域R语言,函数g() 中自由变量z 的值由定义g() 的作用域决定。(对于动态作用域,函数中自由变量的值首先在调用函数的作用域中查找,此时f(1)的值应为41。)

  利用静态作用域的特性,可以将数据缓存在函数中,对于一些可能会对同一组数据调用多次的耗时计算,在第一次计算时将结果缓存下来,之后再使用相同的数据调用函数,则直接从缓存中读取结果直接返回。例如对于矩阵的求逆运算,实现如下:

makeCacheMatrix <- function(x = matrix()) {
  i <- NULL
  set <- function(y) {
    x <<- y
    i <<- NULL
  }
  get <- function() x
  setinverse <- function(inverse) i <<- inverse
  getinverse <- function() i
  list(set = set, get = get,
       setinverse = setinverse,
       getinverse = getinverse)
}

cacheSolve <- function(x, ...) {
  i <- x$getinverse()
  if(!is.null(i)) {
    message("getting cached data")
    return(i)
  }
  data <- x$get()
  i <- solve(data, ...)
  x$setinverse(i)
  i
}

函数makeCacheMatrix() 中定义了用于缓存矩阵的逆的变量i ,通过setinverse() 和getinverse() 进行赋值和读取。setinverse() 和getinverse() 中都没有定义变量i ,它们使用的变量i 都是 makeCacheMatrix() 中定义的i 。函数makeCacheMatrix() 根据输入的矩阵x ,用于创建一个特殊的矩阵数据,包含矩阵的逆的缓存以及对应的设置和获取方法,cacheSolve() 计算矩阵的逆,并进行缓存:

testMatrix <- matrix(c(2,1,0,1), nrow = 2)
m <- makeCacheMatrix(testMatrix)
cacheSolve(m)
cacheSolve(m)

输出为:

> cacheSolve(m)
     [,1] [,2]
[1,]  0.5    0
[2,] -0.5    1
> cacheSolve(m)
getting cached data
     [,1] [,2]
[1,]  0.5    0
[2,] -0.5    1

 可见第一次调用cacheSolve(m) 时,计算、缓存并返回了矩阵的逆,第二次调用cacheSolve(m) 则直接返回缓存中读取结果。