5

我是 R 中面向对象编程的新手,并且不知道如何正确编写修改对象的函数。

这个例子有效:

store1 <- list(
  apples=3,
  pears=4,
  fruits=7
)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
  paste(x$apples, "apples and", x$pears, "pears", sep=" ")
}
print(store1)
addApples <- function(x, i) {
x$apples <- x$apples + i
x$fruits <- x$apples + x$pears
return(x)
}
store1 <- addApples(store1, 5)
print(store1)

但我认为应该有一种更简洁的方法来做到这一点而不返回整个对象:

addApples(store1, 5)  # Preferable line...
store1 <- addApples(store1, 5)  # ...instead of this line

在 R 中编写修改函数的正确方法是什么?“<<-”?

更新:感谢大家在 R 中成为 OOP 的 Rosetta Stone。非常有用。我试图解决的问题在流程方面非常复杂,因此参考类的刚性可能会带来结构的帮助。我希望我能接受所有的回答作为答案,而不仅仅是一个。

4

4 回答 4

4

如果您不想深入参考类,您实际上可以使用具有替换功能的 S3 类来执行此操作。首先,你的例子

store1 <- list(apples=3,pears=4)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
  x <- paste(unlist(store1), names(store1), collapse=", ")
  x <- paste0(x, " for a total of ", sum(unlist(store1)), " fruit.")
  NextMethod()
}
store1
# [1] "3 apples, 4 pears for a total of 7 fruit."

请注意 usingNextMethod意味着我不必这样做print(store1),我只需键入store. 基本上,一旦我重新指定x要在屏幕上显示的内容,我只需发送默认print方法。然后:

`addapples<-` <- function(x, ...) UseMethod("addapples<-")
`addapples<-.fruitstore` <- function(x, value) {
  x[["apples"]] <- x[["apples"]] + value
  x
}
addapples(store1) <- 4
store1
# [1] "7 apples, 4 pears for a total of 11 fruit."

多田!同样,并不是典型的 S3 用例。除了[and[[函数,替换函数通常用于更新属性(例如类、长度等),但我认为扩展它并没有太大的危害。

请注意,这不是真正的引用分配。实际上,您的fruitstore对象被复制、修改并重新分配给原始变量(请参阅R Docs)。

于 2014-01-20T21:56:43.650 回答
3

你应该看看data.table 包

加载包:library(data.table)

定义一个 data.table 对象:

store1 <- data.table(apples=3,
                 pears=4,
                 fruits=7)

然后定义函数addApple:

 addApple <- function(data,i) {data[,apples:=(data[1,apples]+i)]; 
                             data[,fruits:=(data[1,apples]+data[1,pears])]}

就是这样。

当你写作时,addApple(store1)你应该得到 +i 苹果和“苹果 + 梨”水果。

如果你愿意,你仍然可以定义你的 S3 类,只要确保它继承了 data.frame 和 data.table 类。

class(store1) <- c("fruitstore",class(store1))

还有一件事,您应该通过显式打印来重写您的 S3 打印方法:

print.fruitstore <- function(x) {
   print(paste(x$apples, "apples and", x$pears, "pears", sep=" "))
}

或者你可以只使用 cat:

print.fruitstore <- function(x) {cat(x$apples, "apples and", x$pears, "pears")}
于 2014-01-20T21:10:11.873 回答
3

正如其中一条评论所建议的,这是一个参考类实现。基本思想是设置一个名为的引用类,该类Stores具有三个字段:applespearsfruits编辑为访问器方法)。该initialize方法用于初始化一个新的商店,该addApples方法将苹果添加到商店中,而该show方法等价print于其他对象。

Stores = setRefClass("Stores", 
  fields = list(
    apples = "numeric",
    pears  = "numeric",
    fruits = function(){apples + pears}
  ), 
  methods = list(
    initialize = function(apples, pears){
      apples <<- apples
      pears <<- pears
    },
    addApples = function(i){
      apples <<- apples + i
    },
    show = function(){
      cat(apples, "apples and", pears, "pears")
    }
  )
)

如果我们初始化一个新的 store 并调用它,这就是我们得到的

FruitStore = Stores$new(apples = 3, pears = 4)
FruitStore

# 3 apples and 4 pears

现在,调用该addApples方法,让我们将 4 个苹果添加到商店

FruitStore$addApples(4)
FruitStore

# 7 apples and 4 pears

编辑。根据 Hadley 的建议,我已经更新了我的答案,fruits现在它是一种访问器方法。随着我们apples向商店添加更多内容,它会保持更新。谢谢@hadley。

于 2014-01-20T21:33:24.040 回答
2

这是一个使用proto 包的实现,它将对象和类统一为原型的单一概念。例如,这里实际上没有区别,Fruitstore哪个是扮演类角色的store1对象,哪个是扮演特定商店角色的对象。它们都是原型对象。

library(proto)

Fruitstore <- proto(
    addApples = function(., i) {
        .$apples <- .$apples + i
        .$fruits <- .$apples + .$pears
    },
    print = function(.) cat(.$apples, "apples and", .$pears, "pears\n")
)

# define store1 as a child of Fruitstore
store1 <- Fruitstore$proto(apples = 3, pears = 4, fruits = 7)

store1$addApples(5)
store1$print()
于 2014-01-20T22:07:06.350 回答