【r<-高級(jí)】R-面向?qū)ο缶幊蹋ㄒ唬?/h2>

內(nèi)容:

  • S3

借助面向?qū)ο蟮木幋a風(fēng)格,并加以合理的抽象,我們可以簡單地模仿對(duì)象的重要特性,于是,問題和模型之間的轉(zhuǎn)換就變得清晰自然。

S3對(duì)象

S3對(duì)象系統(tǒng)是一個(gè)簡單且寬松的面向?qū)ο笙到y(tǒng)。每個(gè)基本對(duì)象的類型都有一個(gè)S3類名稱。比如integer,numeric, character, logical, listdata.frame都屬于S3類。

舉例,下面vec1類型是double,意味其內(nèi)部類型或者說存儲(chǔ)模式是雙精度浮點(diǎn)型數(shù)字。但它的類是numeric。

vec1 = c(1, 2, 3)typeof(vec1)#> [1] "double"class(vec1)#> [1] "numeric"

下面data1類型是list,意味data1的內(nèi)部類型或者存儲(chǔ)模式是列表,但它的S3類是data.frame。

data1 = data.frame(x = 1:3, y = rnorm(3))typeof(data1)#> [1] "list"class(data1)#> [1] "data.frame"

理解對(duì)象的內(nèi)部類型S3類區(qū)別是一個(gè)重點(diǎn)。

一個(gè)類可以用多種方法定義它的行為,尤其是它與其他類的關(guān)系。在S3系統(tǒng)中,我們可以創(chuàng)建泛型函數(shù)(generic function),對(duì)于不同的類,由泛型函數(shù)決定調(diào)用哪個(gè)方法,這就是S3方法分派(method dispatch)的工作機(jī)理。

對(duì)象的類不同,其方法分派不同,因此,區(qū)別對(duì)象的類十分重要。

R中有許多基于某個(gè)通用目的定義的S3泛型函數(shù),我們先看看head()tail()。head()展示一個(gè)數(shù)據(jù)對(duì)象的前n條記錄,tail()展示后n條。這跟x[1:n]是不同的,因?yàn)閷?duì)不同的類的對(duì)象,記錄的定義是不同的。對(duì)原子向量(數(shù)值、字符向量等),前n條記錄指前n個(gè)元素。但對(duì)于數(shù)據(jù)框,前n條記錄指前n行而不是前n列。

查看下head的函數(shù)內(nèi)部信息:

head#> function (x, ...) #> UseMethod("head")#> <bytecode: 0x0000000018fcb138>#> <environment: namespace:utils>

我們發(fā)現(xiàn)函數(shù)中并沒有實(shí)際的操作細(xì)節(jié)。它調(diào)用UseMethod("head")來讓泛型函數(shù)head()執(zhí)行方法分派,也就是說,對(duì)于不同的類,它可能有不同的執(zhí)行方式(過程)。

num_vec = c(1, 2, 3, 4, 5)data_frame = data.frame(x = 1:5, y = rnorm(5))

調(diào)用函數(shù):

head(num_vec, 3)#> [1] 1 2 3head(data_frame, 3)#>   x     y#> 1 1 0.537#> 2 2 1.072#> 3 3 0.181

我們可以使用methods()查看head()函數(shù)可以實(shí)現(xiàn)的所有方法:

methods("head")#> [1] head.data.frame* head.default*    head.ftable*     head.function*  #> [5] head.matrix      head.table*     #> see '?methods' for accessing help and source code

可以看到head不僅僅適用于向量和數(shù)據(jù)框。

注意,方法都是以method.class形式表示,如果我們輸入一個(gè)data.frame,head()會(huì)調(diào)用head.data.frame方法。當(dāng)沒有方法可以匹配對(duì)象的類時(shí),函數(shù)會(huì)自動(dòng)轉(zhuǎn)向method.default方法。這就是方法分派的一個(gè)實(shí)際過程。

內(nèi)置類和方法

S3泛型函數(shù)和方法在統(tǒng)一各個(gè)模型的使用方式上是最有用的。比如我們可以創(chuàng)建一個(gè)線性模型,以不同角度查看模型信息:

lm1 = lm(mpg ~ cyl + vs, data = mtcars)

線性模型本質(zhì)上是由模型擬合產(chǎn)生的數(shù)據(jù)字段構(gòu)成的列表,所以lm1的類型是list,但是它的類是lm,因此泛型函數(shù)根據(jù)lm選擇方法:

typeof(lm1)#> [1] "list"class(lm1)#> [1] "lm"

甚至沒有明確調(diào)用S3泛型函數(shù)時(shí),S3方法分派也會(huì)自動(dòng)進(jìn)行。如果我們輸入lm1

lm1#> #> Call:#> lm(formula = mpg ~ cyl + vs, data = mtcars)#> #> Coefficients:#> (Intercept)          cyl           vs  #>      39.625       -3.091       -0.939

實(shí)際上,print()函數(shù)被默默地調(diào)用了:

print(lm1)#> #> Call:#> lm(formula = mpg ~ cyl + vs, data = mtcars)#> #> Coefficients:#> (Intercept)          cyl           vs  #>      39.625       -3.091       -0.939

為什么打印出來的不像列表呢?因?yàn)?code>print()是一個(gè)泛型函數(shù),它為lm選擇了一個(gè)方法來打印線性模型最重要的信息。我們可以調(diào)用getS3method("print", "lm")獲取實(shí)際使用的方法與想象的進(jìn)行驗(yàn)證:

identical(getS3method("print", "lm"), stats:::print.lm)#> [1] TRUE

print()展示模型的一個(gè)簡要版本,summary()展示更詳細(xì)的信息。summary()也是一個(gè)泛型函數(shù),它為模型的所有類提供了許多方法:

summary(lm1)#> #> Call:#> lm(formula = mpg ~ cyl + vs, data = mtcars)#> #> Residuals:#>    Min     1Q Median     3Q    Max #> -4.923 -1.953 -0.081  1.319  7.577 #> #> Coefficients:#>             Estimate Std. Error t value Pr(>|t|)    #> (Intercept)   39.625      4.225    9.38  2.8e-10 ***#> cyl           -3.091      0.558   -5.54  5.7e-06 ***#> vs            -0.939      1.978   -0.47     0.64    #> ---#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1#> #> Residual standard error: 3.25 on 29 degrees of freedom#> Multiple R-squared:  0.728,  Adjusted R-squared:  0.71 #> F-statistic: 38.9 on 2 and 29 DF,  p-value: 6.23e-09

實(shí)際上,summary()的輸出結(jié)果也是一個(gè)對(duì)象,包含的數(shù)據(jù)都可以被訪問。在這個(gè)例子里,這個(gè)對(duì)象是一個(gè)列表,是summary.lm類,它有可供print()選擇的自己的方法:

lm1summary = summary(lm1)typeof(lm1summary)#> [1] "list"class(lm1summary)#> [1] "summary.lm"

查看列表成分:

names(lm1summary)#>  [1] "call"          "terms"         "residuals"     "coefficients" #>  [5] "aliased"       "sigma"         "df"            "r.squared"    #>  [9] "adj.r.squared" "fstatistic"    "cov.unscaled"

還有一些其他有用的且與模型相關(guān)的泛型函數(shù),例如plot(),predict()。不同的內(nèi)置模型和第三方擴(kuò)展包提供的模型都能實(shí)現(xiàn)這些泛型函數(shù)。

舉例,我們可以對(duì)線性模型調(diào)用plot()函數(shù):

oldpar = par(mfrow = c(2, 2))plot(lm1)

img

par(oldpar)

為避免依次生成這4個(gè)圖,我們用par()將繪圖區(qū)域劃分為2x2的子區(qū)域。

利用predict()我們可以使用模型對(duì)新數(shù)據(jù)進(jìn)行預(yù)測,泛型函數(shù)predict()自動(dòng)選擇正確的方法用新數(shù)據(jù)進(jìn)行預(yù)測:

predict(lm1, data.frame(cyl = c(6, 8), vs = c(1, 1)))#>    1    2 #> 20.1 14.0

這個(gè)函數(shù)既可以用在樣本內(nèi),又可以用在樣本外。如果我們?yōu)槟P吞峁┬聰?shù)據(jù),它就進(jìn)行樣本外預(yù)測。

下面我們創(chuàng)建一幅真實(shí)值和擬合值的散點(diǎn)圖,看一看線性模型的預(yù)測效果:

plot(mtcars$mpg, fitted(lm1))

img

這里的fitted()也是泛型函數(shù),等價(jià)于lm1$fitted.values,擬合值等于用原始數(shù)據(jù)得到的預(yù)測值,即用原始數(shù)據(jù)構(gòu)建的模型預(yù)測原始數(shù)據(jù),predict(lm1, mtcars)。

真實(shí)值與擬合值的差稱為殘差,可以通過另一個(gè)泛型函數(shù)residuals()獲得。

plot(density(residuals(lm1)),     main = "Density of lm1 residuals")

img

這些泛型函數(shù)不僅適用于lmglm和其他內(nèi)置模型,也適用于其他擴(kuò)展包提供的模型。

例如我們使用rpart包,使用前面的數(shù)據(jù)和公式擬合一個(gè)回歸樹模型。

if(!require("rpart")) install.packages("rpart")#> 載入需要的程輯包:rpartlibrary(rpart)
tree_model = rpart(mpg ~ cyl + vs, data = mtcars)

我們之所以能夠使用相同的方法,是因?yàn)檫@個(gè)包的作者希望函數(shù)調(diào)用的方式與調(diào)用R內(nèi)置函數(shù)保持一致。

typeof(tree_model)#> [1] "list"class(tree_model)#> [1] "rpart"

打印模型:

print(tree_model)#> n= 32 #> #> node), split, n, deviance, yval#>       * denotes terminal node#> #> 1) root 32 1130.0 20.1  #>   2) cyl>=5 21  198.0 16.6  #>     4) cyl>=7 14   85.2 15.1 *#>     5) cyl< 7 7   12.7 19.7 *#>   3) cyl< 5 11  203.0 26.7 *

更詳細(xì)信息:

summary(tree_model)#> Call:#> rpart(formula = mpg ~ cyl + vs, data = mtcars)#>   n= 32 #> #>       CP nsplit rel error xerror   xstd#> 1 0.6431      0     1.000  1.089 0.2579#> 2 0.0893      1     0.357  0.432 0.0811#> 3 0.0100      2     0.268  0.427 0.0818#> #> Variable importance#> cyl  vs #>  65  35 #> #> Node number 1: 32 observations,    complexity param=0.643#>   mean=20.1, MSE=35.2 #>   left son=2 (21 obs) right son=3 (11 obs)#>   Primary splits:#>       cyl < 5   to the right, improve=0.643, (0 missing)#>       vs  < 0.5 to the left,  improve=0.441, (0 missing)#>   Surrogate splits:#>       vs < 0.5 to the left,  agree=0.844, adj=0.545, (0 split)#> #> Node number 2: 21 observations,    complexity param=0.0893#>   mean=16.6, MSE=9.45 #>   left son=4 (14 obs) right son=5 (7 obs)#>   Primary splits:#>       cyl < 7   to the right, improve=0.507, (0 missing)#>   Surrogate splits:#>       vs < 0.5 to the left,  agree=0.857, adj=0.571, (0 split)#> #> Node number 3: 11 observations#>   mean=26.7, MSE=18.5 #> #> Node number 4: 14 observations#>   mean=15.1, MSE=6.09 #> #> Node number 5: 7 observations#>   mean=19.7, MSE=1.81

下面對(duì)結(jié)果進(jìn)行可視化,得到樹圖:

oldpar = par(xpd = NA)plot(tree_model)text(tree_model, use.n = TRUE)

img

par(oldpar)

為現(xiàn)有類定義泛型函數(shù)

在定義泛型函數(shù)時(shí),我們創(chuàng)建一個(gè)函數(shù)去調(diào)用UseMethod()出發(fā)方法分派。然后對(duì)泛型函數(shù)想要作用的類創(chuàng)建帶有method.class形式的方法函數(shù),同時(shí)還要?jiǎng)?chuàng)建帶有method.default形式的默認(rèn)方法來應(yīng)對(duì)所有其他情況。

下面我們創(chuàng)建一個(gè)新的泛型函數(shù)generic_head(),它有兩個(gè)參數(shù):輸入對(duì)象x和需要提取的記錄條數(shù)n。泛型函數(shù)僅僅調(diào)用UseMethod("generic_head")來讓R根據(jù)輸入對(duì)象x的類執(zhí)行方法分派。

generic_head = function(x, n)    UseMethod("generic_head")

對(duì)原子向量提取前n個(gè)元素,因此分別定義generic_head.numeric、generic_head.character等,另外最好定義一個(gè)默認(rèn)方法捕獲不能匹配的其他所有情況:

generic_head.default = function(x, n){    x[1:n]}

現(xiàn)在generic_head只有一種方法,等于沒有使用泛型函數(shù):

generic_head(num_vec, 3)#> [1] 1 2 3

現(xiàn)在我們還沒有定義針對(duì)data.frame類的方法,所以當(dāng)我們輸入數(shù)據(jù)框時(shí),函數(shù)會(huì)自動(dòng)轉(zhuǎn)向generic_head.default,又因?yàn)樘崛〉臄?shù)量超出列數(shù),所以下面的運(yùn)行會(huì)報(bào)錯(cuò):

generic_head(data_frame, 3)#> Error in `[.data.frame`(x, 1:n): 選擇了未定義的列

下面為data.frame定義方法:

generic_head.data.frame = function(x, n) {    x[1:n, ]}

現(xiàn)在函數(shù)就可以正常運(yùn)行了:

generic_head(data_frame, 3)#>   x     y#> 1 1 0.537#> 2 2 1.072#> 3 3 0.181

因?yàn)闆]有對(duì)參數(shù)進(jìn)行檢查,所以S3類執(zhí)行的方法并不穩(wěn)健。

定義新類并創(chuàng)建對(duì)象

現(xiàn)在我們來嘗試構(gòu)建新類,class(x)獲取x的類,而class(x) = some_classx的類設(shè)為some_class

使用列表作為底層數(shù)據(jù)結(jié)構(gòu)

列表可能是創(chuàng)建新類時(shí)使用最廣泛的數(shù)據(jù)結(jié)構(gòu),類描述了對(duì)象的類型和對(duì)象交互作用的方法,其中對(duì)象用于存儲(chǔ)多種多樣、長度不一的數(shù)據(jù)。

下面我們定義一個(gè)叫product的函數(shù),創(chuàng)建一個(gè)由name、priceinventory構(gòu)成的列表,該列表的類是product。我們還將自己定義它的print方法。

productor = function(name, price, inventory){    obj = list(name = name,               price = price,               inventory = inventory)    class(obj) = "product"    obj}

上面我們創(chuàng)建了一個(gè)列表,然后將它的類替換為product。我們還可以使用structure()

product = function(name, price, inventory){    structure(list(name = name,              price = price,              inventory = inventory),              class = "product")}

現(xiàn)在我們調(diào)用product()函數(shù)生成product類的實(shí)例:

laptop = product("Laptop", 499, 300)

查看它的結(jié)構(gòu)和S3類方法分派:

typeof(laptop)#> [1] "list"class(laptop)#> [1] "product"

此時(shí)我們還沒有為該類定義任何方法,如果print將按默認(rèn)列表輸出:

print(laptop)#> $name#> [1] "Laptop"#> #> $price#> [1] 499#> #> $inventory#> [1] 300#> #> attr(,"class")#> [1] "product"

下面我們自定義一個(gè)print方法,使得輸出更緊湊:

print.product = function(x, ...){    cat("<product>\n")    cat("name:", x$name, "\n")    cat("price:", x$price, "\n")    cat("inventory:", x$inventory, "\n")    invisible(x)}

print方法返回輸入對(duì)象本身以備后用,這是一項(xiàng)約定。

現(xiàn)在我們?cè)賮砜纯摧敵觯?/p>

laptop#> <product>#> name: Laptop #> price: 499 #> inventory: 300

我們可以像操作列表一樣訪問laptop的成分:

laptop$name#> [1] "Laptop"laptop$price#> [1] 499laptop$inventory#> [1] 300

如果我們創(chuàng)建另一個(gè)對(duì)象,并將兩者放入一個(gè)列表然后打印,print.product仍然會(huì)被調(diào)用:

cellphone = product("Phone", 249, 12000)products = list(laptop, cellphone)products#> [[1]]#> <product>#> name: Laptop #> price: 499 #> inventory: 300 #> #> [[2]]#> <product>#> name: Phone #> price: 249 #> inventory: 12000

當(dāng)products以列表形式被打印時(shí),會(huì)對(duì)每個(gè)元素調(diào)用print()泛型函數(shù),再由泛型函數(shù)執(zhí)行方法分派。

大多數(shù)其他編程語言都對(duì)類有正式的定義,而S3沒有,所以創(chuàng)建一個(gè)S3對(duì)象比較簡單,但我們需要對(duì)輸入?yún)?shù)進(jìn)行充分的檢查,以確保創(chuàng)建的對(duì)象與所屬類內(nèi)部一致

除了定義新類,我們還可以定義新的泛型函數(shù)。下面創(chuàng)建一個(gè)叫value的泛型函數(shù),它通過測量產(chǎn)品的庫存值來為product調(diào)用實(shí)施方法:

value = function(x, ...)    UseMethod("value")value.default = function(x, ...){    stop("Value is undefined")}value.product = function(x, ...){    x$price * x$inventory}

針對(duì)其他類,value調(diào)用default方法并終止運(yùn)行。

value(laptop)#> [1] 149700value(cellphone)#> [1] 2988000value(data_frame)#> Error in value.default(data_frame): Value is undefined

使用原子向量作為底層數(shù)據(jù)結(jié)構(gòu)

上面我們已經(jīng)演示了創(chuàng)建S3類和泛型函數(shù)的過程,有時(shí)候我們需要使用原子向量創(chuàng)建新類,下面展示百分比形式向量創(chuàng)建過程。

首先定義一個(gè)percent函數(shù),它檢查輸入是否是數(shù)值向量并將輸入對(duì)象類型改為percent,percent類繼承numeric類:

percent = function(x){    stopifnot(is.numeric(x))    class(x) = c("percent", "numeric")    x}

這里的繼承指方法分派首先在percent類中方法找,找不到就去numeric類方法中找。尋找的順序由類名稱的順序決定。

pct = percent(c(0.1, 0.05, 0.25))pct#> [1] 0.10 0.05 0.25#> attr(,"class")#> [1] "percent" "numeric"

現(xiàn)在定義方法,讓percent類以百分比形式存在:

as.character.percent = function(x, ...){    paste0(as.numeric(x) * 100, "%")}

現(xiàn)在我們可以得到字符型了:

as.character(pct)#> [1] "10%" "5%"  "25%"

也可以直接調(diào)用as.character()percent提供一個(gè)format方法:

format.percent = function(x, ...){    as.character(x, ...)}

format現(xiàn)在有相同的效果:

format(pct)#> [1] "10%" "5%"  "25%"

類似地,我們調(diào)用format.percent()percent提供print方法:

print.percent = function(x, ...){    print(format.percent(x), quote = FALSE)}

這里指定quote=FALSE使得打印的格式化字符串更像數(shù)字而非字符串。

pct#> [1] 10% 5%  25%

注意,使用算術(shù)運(yùn)算符操作后會(huì)自動(dòng)保持輸出向量類不變:

pct + 0.2#> [1] 30% 25% 45%pct * 0.5#> [1] 5%    2.5%  12.5%

可惜使用其他函數(shù)可能不會(huì)保持輸入對(duì)象的類,比如sum()、mean()等:

sum(pct)#> [1] 0.4mean(pct)#> [1] 0.133max(pct)#> [1] 0.25min(pct)#> [1] 0.05

為了確保百分比形式保存,我們對(duì)percent類實(shí)施一些操作:

sum.percent = function(...){    percent(NextMethod("sum"))}mean.percent = function(x, ...){    percent(NextMethod("mean"))}max.percent = function(...){    percent(NextMethod("max"))}min.percent = function(...){    percent(NextMethod("max"))}

NextMethod("sum")對(duì)numeric類調(diào)用sum()函數(shù),然后再調(diào)用percent()函數(shù)將輸出的數(shù)值向量包裝為百分比形式:

sum(pct)#> [1] 40%mean(pct)#> [1] 13.3333333333333%max(pct)#> [1] 25%min(pct)#> [1] 5%

但如果我們組合一個(gè)百分比向量和其他數(shù)值型的值,percent類又會(huì)消失掉,我們進(jìn)行相同的改進(jìn):

c.percent = function(x, ...){    percent(NextMethod("c"))}
c(pct, 0.12)#> [1] 10% 5%  25% 12%

dan….我們?nèi)∽蛹謺?huì)有問題

pct[1:3]#> [1] 0.10 0.05 0.25pct[[2]]#> [1] 0.05

同樣地,我們對(duì)[[[函數(shù)進(jìn)行改造:

`[.percent` = function(x, i) {    percent(NextMethod('['))}`[[.percent` = function(x, i){    percent(NextMethod("[["))}

此時(shí)顯示就正常了:

pct[1:3]#> [1] 10% 5%  25%pct[[2]]#> [1] 5%

實(shí)現(xiàn)這些方法后,我們可以在數(shù)據(jù)框中使用:

data.frame(id = 1:3, pct)#>   id pct#> 1  1 10%#> 2  2  5%#> 3  3 25%

S3繼承

假設(shè)我們想要對(duì)一些交通工具,例如汽車、公共汽車和飛機(jī)進(jìn)行建模。這些交通工具有一些共性,它們都有名稱、速度、位置,而且都可以移動(dòng)。為了形象化描述它們,我們定義一個(gè)基本類,稱為vehichle,用于存儲(chǔ)公共部分,另外定義carbusairplane這3個(gè)子類,它們繼承vehichle,但具有自定義的行為。

首先,定義一個(gè)函數(shù)來創(chuàng)建vehicle對(duì)象,它本質(zhì)上是一個(gè)環(huán)境。我們選擇環(huán)境而不是列表,因?yàn)樾枰玫江h(huán)境的引用語義,也就是說,我們傳遞一個(gè)對(duì)象,然后原地修改它,而不會(huì)創(chuàng)建這個(gè)對(duì)象的副本。因此無論什么位置將對(duì)象傳遞給函數(shù),對(duì)象總是指向同一個(gè)交通工具。

Vehicle = function(class, name, speed) {    obj = new.env(parent = emptyenv())    obj$name = name    obj$speed = speed    obj$position = c(0, 0, 0)    class(obj) = c(class, "vehicle")    obj}

這里的class(obj) = c(class, "vehicle")似乎有點(diǎn)語義不明。但前者是基礎(chǔ)函數(shù),后者是輸入?yún)?shù),R能夠判斷好。

下面函數(shù)創(chuàng)建繼承vehiclecar、busairplane的特定函數(shù):

Car = function(...){    Vehicle(class = "car", ...)}Bus = function(...){    Vehicle(class = "bus", ...)}Airplane = function(...){    Vehicle(class = "airplane", ...)}

現(xiàn)在我們可以為每一個(gè)子類創(chuàng)建實(shí)例:

car = Car("Model-A", 80)bus = Bus("Medium-Bus", 40)airplane = Airplane("Big-Plane", 800)

下面為vehicle提供通用的print方法:

print.vehicle = function(x, ...){    cat(sprintf("<vehicle: %s>\n", class(x)[1]))    cat("name:", x$name, "\n")    cat("speed:", x$speed, "km/h\n")    cat("position:", paste(x$position, collapse = ", "), "\n")}

因?yàn)槲覀兌x的3個(gè)子類都有了繼承,所以print方法通用:

car#> <vehicle: car>#> name: Model-A #> speed: 80 km/h#> position: 0, 0, 0bus#> <vehicle: bus>#> name: Medium-Bus #> speed: 40 km/h#> position: 0, 0, 0airplane#> <vehicle: airplane>#> name: Big-Plane #> speed: 800 km/h#> position: 0, 0, 0

因?yàn)榻煌üぞ呖梢砸苿?dòng),我們創(chuàng)建一個(gè)泛型函數(shù)move來表征這樣的狀態(tài):

move = function(vehicle, x, y, z) {    UseMethod("move")}move.vehicle = function(vehicle, movement) {    if (length(movement) != 3){        stop("All three dimensions must be specified to move a vehicle")    }        vehicle$position = vehicle$position + movement    vehicle}

這里我們將汽車和公共汽車的移動(dòng)限定在二維平面上。

move.bus = move.car = function(vehicle, movement) {    if (length(movement) != 2){        stop("This vehicle only supports 2d movement")    }        movement = c(movement, 0)    NextMethod("move")}

這里我們將movement的第3個(gè)緯度強(qiáng)制轉(zhuǎn)換為0,然后調(diào)用NextMethod("move")來調(diào)用move.vehicle()。

飛機(jī)既可以在2維也可以在3維:

move.airplane = function(vehicle, movement) {    if (length(movement) == 2){        movement = c(movement, 0)    }        NextMethod("move")}

下載3種方法都實(shí)現(xiàn)了,進(jìn)行測試。

move(car, c(1, 2, 3))#> Error in move.car(car, c(1, 2, 3)): This vehicle only supports 2d movement

只能輸入二維,所以提示報(bào)錯(cuò)了。

move(car, c(1, 2))#> <vehicle: car>#> name: Model-A #> speed: 80 km/h#> position: 1, 2, 0
move(airplane, c(1, 2))#> <vehicle: airplane>#> name: Big-Plane #> speed: 800 km/h#> position: 1, 2, 0

飛機(jī),3維:

move(airplane, c(20,100,50))#> <vehicle: airplane>#> name: Big-Plane #> speed: 800 km/h#> position: 21, 102, 50

注意,airplane的位置是累積的。因?yàn)榍懊嬲f過,它本質(zhì)是一個(gè)環(huán)境,因此修改move.vehicle()中的position不會(huì)創(chuàng)建一個(gè)副本再修改,而是本地修改!

學(xué)習(xí)自《R語言編程指南》


內(nèi)容太多,下次學(xué)習(xí)接下來的內(nèi)容。

文章作者 王詩翔

上次更新 2018-08-15

許可協(xié)議 CC BY-NC-ND 4.0

class instance S3 S4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容