假设我有一个带有两个插槽的S4类。然后我创建一个方法,将其中一个槽设置为某个并返回结果。另一个插槽是否也会在作业时被复制?
例如,
setClass('foo', representation(first.slot = 'numeric', second.slot = 'numeric'))
setGeneric('setFirstSlot', function(object, value) {standardGeneric('setFirstSlot')})
setMethod('setFirstSlot', signature('foo', 'numeric'), function(object, value) {
object@first.slot = value
return(object)
})
f <- new('foo')
f@second.slot <- 2
f <- setFirstSlot(f, 1)
在最后一行,是否会复制第一个和第二个插槽的值,还是会进行某种优化?我有一个类,其中一个字段包含一个千兆字节的数据和一些带有小数字向量的字段,我想为数字字段设置一个setter函数,不会浪费时间在每次使用时不必要地复制数据。 / p>
谢谢:)
答案 0 :(得分:3)
如果要在字段中复制大量数据,则一种解决方案是使用引用类。让我们将参考类与S4进行比较。
## Store timing output
m = matrix(0, ncol=4, nrow=6)
创建一个函数类定义:
foo_ref = setRefClass("test", fields = list(x = "numeric", y = "numeric"))
然后分配时间数据:
## Reference class
g = function(x) {x$x[1] = 1; return(x)}
for(i in 6:8){
f = foo_ref$new(x = 1, y = 1)
y = runif(10^i)
t1 = system.time({f$y <- y})[3]
t2 = system.time({f$y[1] = 1})[3]
t3 = system.time({f$x = 1})[3]
t4 = system.time({g(f)})[3]
m[i-5, ] = c(t1, t2, t3, t4)
}
我们可以重复类似的S4结构:
g = function(x) {x@y[1] = 1; return(x)}
setClass('foo_s4', representation(x = 'numeric', y = 'numeric'))
for(i in 6:8){
f = new('foo_s4'); f@x = 1; f@y = 1
y = runif(10^i)
t1 = system.time({f@y <- y})[3]
t2 = system.time({f@y[1] <- 1})[3]
t3 = system.time({f@x = 1})[3]
t4 = system.time({g(f)})[3]
m[i-2, ] = c(t1, t2, t3, t4)
}
使用大数据集的引用类结构进行赋值在处理函数时效率更高。
t3
S4对象的时间更高。答案 1 :(得分:3)
当开发人员使用该类(谁知道类的设计)时,使用赋值运算符@<-
而不是问题中定义的setFirstSlot
的setter方法可能会更好。原因是前者避免返回整个对象。
但是,需要使用setter方法来防止用户尝试与类中插槽的定义不匹配的分配。我知道如果我们使用@<-
将字符分配给插槽x
(定义为numeric
),则会返回错误。
setClass('foo', representation(x = 'numeric', y = 'numeric'))
f <- new('foo')
f@x <- 1 # this is ok
f@y <- 2 # this is ok
f@x <- "a"
#Error in checkAtAssignment("foo", "x", "character") :
# assignment of an object of class “character” is not valid for @‘x’ in an object of class “foo”; is(value, "numeric") is not TRUE
但想象一下插槽应该只包含一个元素的情况。 @<-
:
# this assignment is allowed
f@x <- c(1, 2, 3, 4)
f@x
#[1] 1 2 3 4
在这种情况下,我们想要定义一个setter方法,以便告知用户有关插槽定义的进一步限制。但是,我们必须返回整个对象,如果对象很大,这可能是一个额外的负担。
据我所知,无法在其定义中定义槽的长度。可以定义方法setValidity
以检查广告位中的此要求或其他要求,但似乎@<-
不依赖于validObject
,而作业f@x <- c(1, 2, 3, 4)
将是即使我们定义setValidity
:
valid.foo <- function(object)
{
if (length(object@x) > 1)
stop("slot ", sQuote("x"), " must be of length 1")
}
setValidity("foo", valid.foo)
# no error is detected and the assignment is allowed
f@x <- c(1, 4, 6)
f@x
#[1] 1 4 6
# we need to call "validObject" to check if everything is correct
validObject(f)
#Error in validityMethod(object) : slot ‘x’ must be of length 1
可能的解决方案是就地修改对象。以下方法set.x.inplace
基于this approach。
setGeneric("set.x.inplace", function(object, val){ standardGeneric("set.x.inplace") })
setMethod("set.x.inplace", "foo", function(object, val)
{
if (length(val) == 1) {
eval(eval(substitute(expression(object@x <<- val))))
} else
stop("slot ", sQuote("x"), " must be of length 1")
#return(object) # not necessary
})
set.x.inplace(f, 6)
f
#An object of class "foo"
#Slot "x":
#[1] 6
#Slot "y":
#[1] 2
# the assignment is not allowed
set.x.inplace(f, c(1,2,3))
#Error in set.x.inplace(f, c(1, 2, 3)) : slot ‘x’ must be of length 1
由于此方法不执行返回操作,因此对于大尺寸的对象,它可以是一个很好的替代方法。