R语言之S3系统(详细介绍)*程序输出改进

S3

当初建议的*程序应该以如下:

score <- function(symbols){
  same <- symbols[1] == symbols[2] && symbols[2] == symbols[3]
  bars <- symbols %in% c("B","BB","BBB")
  if (same){
    payouts <- c("DD" = 100,"7" = 80,"BBB" = 40,"BB" = 25,"B" = 10, 
                 "C"=10,"0" = 0)
    prize <- unname(payouts[symbols[1]])
  }else if(all(bars)){
    prize <- 5
  }else{
    cherries <- sum(symbols == "C")
    prize <- c(0,2,5)[cherries + 1]
  }
  diamonds <- sum(symbols == "DD")
  prize * 2^ diamonds
}
get_symbols <- function(){
  wheel <- c("DD","7","BBB","BB","B","C","0")
  sample(wheel, size = 3,replace =TRUE,
         prob = c(0.03,0.03,0.06,0.1,0.25,0.01,0.52))
}
play <- function(){
  symbols <- get_symbols()
  print(symbols)
  score(symbols)
}
play()

显示结果:
R语言之S3系统(详细介绍)*程序输出改进
然而我们目前的*程序显示的结果看起来没有上面的美观。
这些问题都可以通过R的S3系统得到解决。

1.S3系统

S3指的是R自带的类系统。这个系统掌管着R如何处理具有不同类的对象。一些函数会首先查询对象的S3类,再根据其类属性作出相应的响应。

print就是这样的一个函数。当你输出一个数值型向量时,print会显示一个数字。

R语言之S3系统(详细介绍)*程序输出改进
但是,如果赋予该数字后面跟着POSIXt的s3类POSIXct,print将显示一个时间:

 class(num)<-c("POSIXct","POSIXt")
 print(num)

R语言之S3系统(详细介绍)*程序输出改进
如果你使用的对象具有类属性,那么就会接触到R的S3系统。

R的S3系统有三个组成部分:属性(attribute)(尤其是class属性)、泛型函数(genericfunction)和方法(method)

2.属性

可以用attributes函数查看一个对象的属性。如果对第二部分中我们所创建的DECK数据框运行attributes函数,将会得到如下结果。

attributes(DECK)

attr接受两个参数:一个R对象和某个属性的名称(以字符串的形式)要赋予R对象具有指定名称的某个属性,需要将某个值保存到attr的输出结果。现在我们赋one_play一个名为symbols的属性,该属性包含一个字符串向量。


attr(one_play,"symbols")<-c("B","0","B")
attributes(one_play)

R语言之S3系统(详细介绍)*程序输出改进
如果想查找某个属性的取值,同样可以利用attr函数,并提供一个R对象的名称和想要查找的属性名称。

attr(one_play,"symbols")

R语言之S3系统(详细介绍)*程序输出改进

修改play函数,使得它在返回中奖金额的同时也能包含相应的*符号的信息,这个信息被包含在一个叫作symbols的属性中将print(symbols)的冗余信息移除。

play<-function(){
  symbols<-get_symbols()
  print(symbols)
  score(symbols)
}
play<-function( ){
  symbols<-get_symbols()
  prize<-score(symbols)
  attr(prize,"symbols")<-symbols
  prize
}#现在play函数会同时返回中奖金额及其对应的符号组合
two_play<-play()

two_play

R语言之S3系统(详细介绍)*程序输出改进
利用structure函数,可以将生成中奖金额和设置属性值合并为一步来完成。

3.泛指函数

每次在控制台窗口显示某个输出结果时R都会调用print函数。
R语言之S3系统(详细介绍)*程序输出改进
print不是一个普通的函数,它是泛型函数。这也就意味着,在不同的场合,print可以完成不同的任务。在显示无类属性的num时,print的显示结果如下。

如果赋给num一个类,print的显示结果便会发生改变。

4.方法

当你调用print函数时,它其实调用了一个特别的函数,叫作UseMethod.

比如说,当你向print提供一个类属性为POSIXct的对象时,UseMethod会将print函数的所有参数交给print.POSIXct函数处理。R随后会运行print.POSIXct函数并返回针对POSIXct类属性的输出结果。

print.POSIXct

如果对一个类属性为因子(factor)的对象调用print函数,UseMethod会将print的所有参数交给print.factor函数来进行处理。R随后会运行print.factor函数并返回结果。
print.POSIXct和print.factor被称为print函数的方法(method)。

这样一个由泛型函数、方法和基于类的分派方式所构成的系统就是R的S3系统。之所以叫作S3是由于它起源于S语言的第三个版本,语言S-PLUS是和R语言的前身。许多常见的R函数都是S3泛型函数,它们可以支持多种不同的类方法函数。比如说,summary和head就会调用UseMethod函数以识别对象的类属性。

   S3系统使得R函数能够在不同的场合有不同的表现。可以利用S3系统进一步美化*程序的输出格式。要实现这一点,首先将类属性赋给输出结果;然后针对该类属性编写一个print类方法。

方法分派

每一个S3方法的名称都包含两个部分。前一部分指明该方法对应的函数,后一部分则指明类属性。这两个部分的名称用英文句点.分隔。
比如说,处理类属性为函数(function)的print方法名print.function,处理类属性为矩阵(matrix)的summary方法名为summary.matrix,以此类推。

修改play函数,使其输出slots结果具有名slots的类属性:

play<-function(){
  symbols<-get_symbols()
  structure(score(symbols),symbols=symbols,class="slots")
}
}

现在每次运行play函数,其输出结果都将具有slots类属性.

R语言之S3系统(详细介绍)*程序输出改进

5.类

可以利用R的S3系统为对象新建一个稳健的类(class)会以一致且合理的方式对待同属一类的对象。要想创建一个类,应该执行以下操作。

(1)给类起一个名称。
(2)给属于该类的每个对象赋class属性。
(3)为属于该类的对象编写常用泛型函数的类方法

可以针对某个类调用methods函数并指定想要查找的class属性作为其参数,也就是一个字符串。methods函数会返回R中已经存在的针对该类的所有方法。但要注意,如果某个R包事先没有经过加载,那么其中的方法就不会出现在methods的返回结果中。

 methods(class="factor")

R语言之S3系统(详细介绍)*程序输出改进

6.S3与调试

在尝试理解R函数时,S3系统可能会给你带来很多烦恼。如果一个函数在其代码中调用了UseMethod,那么就很难知道这个函数真正是做什么的。不过现在已经知道UseMethod本身会调用某个类属性所对应的类方法函数,因此可以直接找到这个类方法函数并检查它的源代码。这个类方法函数的名称应该符合<function.class>的语法结构,或者可能是<function.default>的结构。你还可以使用methods函数查看与某个函数或类相关的方法。

7.S4和R5

R还有另外两个可以用来创建类属性行为的系统。它们是S4系统和R5系统,后者也叫作引用类(reference class)系统。相比于S3系统,这两个系统的使用难度更大,因此也更少见。然而,它们提供了S3系统没有的防护措施。如果想学习更多关于这些系统的内容,包括如何编写和使用自定义泛型函数,推荐读一读HadleyWickham所著的新书Advanced R Programming.

上一篇:dlopen配置RTLD_DEEPBIND


下一篇:System 11-3: Symbols