內(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, list和data.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)
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))
這里的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")
這些泛型函數(shù)不僅適用于lm、glm和其他內(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)
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_class將x的類設(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、price和inventory構(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ǔ)公共部分,另外定義car、bus和airplane這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)建繼承vehicle的car、bus和airplane的特定函數(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




