2014-01-20 3 views
3

Я новичок в объектно-ориентированном программировании в R и борюсь с тем, как правильно писать функцию, которая модифицирует объект.Измените объект S3, не возвращая его?

Этот пример работает:

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? "< < -"?

Обновление: Спасибо всем, что стало камнем Rosetta для ООП в Р. Очень информативно. Проблема, которую я пытаюсь решить, очень сложна с точки зрения потока, поэтому жесткость ссылочных классов может помочь структуре. Хотел бы я принять все ответы как ответы, а не только один.

+6

Если вы действительно хотите изменить на месте, то, возможно, вы не должны использовать объекты S3, но [ссылка класс] (http://adv-r.had.co.nz/OO-essentials. html # rc). – Andrie

+0

Хороший товар. Цель ссылочного класса внезапно имеет смысл. – Chris

ответ

3

Вот ссылка класс как это предлагается в одном из комментариев. Основная идея состоит в том, чтобы создать ссылочный класс Stores, который имеет три поля: apples, pears и fruits (отредактирован как метод доступа). Метод 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") 
    } 
) 
) 

Если инициализировать новый магазин и назвать его, вот что мы получаем

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

# 3 apples and 4 pears 

Теперь, вызвав метод addApples, давайте добавим 4 яблоки в магазине

FruitStore$addApples(4) 
FruitStore 

# 7 apples and 4 pears 

EDIT. Согласно предложению Хэдли, я обновил свой ответ, так что fruits теперь является методом доступа. Он остается обновленным по мере добавления в магазин apples. Спасибо @hadley.

+2

Кажется, что 'fruit' не должно быть полем, а вычисленным методом (или если вы действительно хотите, чтобы вы могли использовать метод доступа, так что« FruitStore $ fruit »все равно работал бы) – hadley

+0

True. Я обновил свой ответ, используя методы доступа. – Ramnath

2

Вы должны взглянуть на data.table package.

Загрузите пакет: 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), вы должны получить фрукты + яблоки и фрукты «яблоки + груши».

И вы все еще можете определить свой класс 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=" ")) 
} 

Или вы могли бы просто использовать кота:

print.fruitstore <- function(x) {cat(x$apples, "apples and", x$pears, "pears")} 
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." 

Обратите внимание, как с помощью NextMethod означает, что я не должен делать 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." 

Tada! Опять же, не типичный пример использования S3. За исключением функций [ и [[, функции замены обычно предназначены для обновления атрибутов (например, класса, длины и т. Д.), Но я не вижу слишком большого вреда в растягивании.

Обратите внимание, что это не настоящее задание по ссылке. Действительно, ваш объект fruitstore копируется, модифицируется и повторно назначается исходной переменной (см. R Docs).

2

Вот реализация с использованием proto package, которая объединяет объекты и классы в единую концепцию прототипов. Например, здесь нет никакой разницы между 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() 
Смежные вопросы