当前课程知识点:R语言数据分析 > 中部:执具 > 第6章 基础编程——用别人的包和函数讲述自己的故事 > 6.6 函数(II)
大家好
欢迎来到《R语言数据分析》课程
咱们今天继续交流函数相关的内容
我们先来看几行代码
就是关于这个plot绘图的几行代码
比如说我们首先可以定义一个数值向量
这个等差数列1到100的交给x
然后再定义另外一个向量就是y <- 2x+10
那我们再x和y来cbind(x, y)一下
就相当于并列放在一起
作为列放一起
就形成一个矩阵
这个时候它的class是一个矩阵matrix
这时候要是我将这个xy交给plot的话
你发现它会自动生成一个
就绘制了一个曲线 [直线]
毫无疑问是条直线
这是我们plot(xy,...)的一个效果
接下来我们还是x和y还是不变
但是我现在拟合了一个关于y和x之间的函数
我通过这个lm=linear model来拟合它
然后拟合结果
交给这个my_model
结果my_model是一个 linear model
是一个线性模型
然后我想将我这个my_model
也交给这个plot进行绘制的话
你会发现它出了
4幅图
同样是plot
但是当我这个
接收的参数这个my_model
以及前面xy
它的class不一样的时候
一个是这个矩阵matrix
一个是线性模型lm
它不一样的时候
你发现这个plot的行为是不一样的
这个plot的行为是不一样的
同样一个接口
然后面对不同的对象的时候
有不同的行为
这其实引出我们今天想讲的主题是
多态的问题
泛型函数的问题
其实也是在这个面向对象里面一个重要特点
就是多态
针对不同的对象有不同的行为
那在R里面的体现的话就是
泛型函数
所谓泛型函数
假如我们用这个影视剧的话
比较通俗的话讲就是
见什么人说什么话
到什么山上唱什么歌
我对象不一样
然后我的方法是不一样
那泛型函数怎么定义
泛型函数其实应该是一组函数
我们说泛型函数它有个接口
接口是统一的
比如说我们现在定义一个函数
一个泛型函数的接口叫interface
就是我接口都叫这个名字
但是当我这个类不一样的时候
我接受参数所属类不一样的时候
然后我有不同的具体的函数
比如说我现在根据这个y
就是我这个参数里面其中一个参数是y
这个y的类型不一样
它的class不一样
我就分发到不同的
particular这个函数里面去
假如我这个y是classA的话
那接下来我调用谁
particular.classA()
假如我这个y的类型是classB的话
那我就要调用particular.classB()
所以我们看得出来点号.在R里面
它可以作为一个简单的字符来对待
但同时它有特殊的含义
比如说点号放在最前面
表示隐藏的一个变量
假如点号放到中间的话
现在我们这个泛型函数定义的时候
其实是一个后缀
用来匹配不同的分发的函数
也就说一旦调用这个interface的时候
它首先进入这个接口
single interface然后通过这个UseMethod()
我来分发到具体方法里面去
当然也可能匹配不到
比如说我这个y
既不属于classA也不属于classB
那它就直接调用这个particular.default()
.default
这其实就是一个
泛型函数定义的一个典型的方式
有一个接口函数
有不同的具体分发函数
当然一般来讲
我们的接口函数interface和
后面这个分发的函数particular
其实我们在很多情况之下二者都是一样
就说这里面是interface那这边也是interface
interface.classA
interface.classB
interface. default
当然因为我们这边为了展示这个详细分发过程
将它二者取值不一样
另外就是假如我这里面默认的情况之下
大部分都是第一个参数
看第一个参数的类别
当然我们这边是将第二个参数的类别
作为一个分发的一个具体的一个机制
就是参照第二个参数所属的类来进行分发
一旦我们定义这么一个泛型函数
或者说这么一组泛型函数的话
那我们怎么调用 我们举个例子
比如x赋值为1到10
y赋值为1到20
然后我现在将y的标签贴成classA
给y贴上标签贴成classA
这时候我调用这个interface(x, y)的时候
大家注意了
它首先是进入这个接口
然后进到这个具体的particular函数里面去是
particular.classA
particular.classA
假如我现在将这个y的标签贴成classB的话
它首先还是进入这个接口函数
这个single interface
但是它的分发函数是多少
大家注意了
应该是particular.classB
所以我们可以看得出来
当类标签不一样的时候贴上不同的标签
然后我的接口是一样的
都是interface(x, y)
但是我具体行为是不一样
一个是particular.classA
一个是particular.classB
这里面其实我们可以看得出来
确实就基本就实现了
针对不同的对象
然后有不同的行为
那我们需要补充一下
大家看这个y的话
应该是一个1到20的一个数值的向量
但这个时候我们发现它所属的类可以随便贴
可以贴成classA也可以贴成classB
你甚至可以贴成某个人的名字
或者贴成“我爱北京天安门”
这么一个字符串也可以贴给它
所以对于R来讲所谓的类
它并不是它本质上存储的一个[数据]类型
它只是一个标签而已
当然假如我这个y的类型
比如说classC的话
那这时候你再调用
这个interface(x, y)
毫无疑问
它就只能进到particular.default()里面去
或者说你撕掉这个标签
将class(y)赋值为多少呢
NULL
那这个时候interface(x, y)
同样 也只能跑到哪去
只能跑到default里面去
particular.default
这其实就是我们说的泛型函数
它的一个定义以及调用的过程
好 我们再看看我们前面代码
结合我们前面代码
看到一些那个泛型已经用到过一些泛型函数
比如这个加号
其实就是一个泛型函数
+就是一个泛型函数
我们前面在讲这个二元操作符的时候
已经讲到加号了
假如我们在没有加载ggplot2包之前
我们有多少个泛型函数
就加点后面有多少个
它可以 +.Date
也可以 +.POSIXt
都可以
假如我们一旦加载完这个ggplot2之后
通过这个library()这个函数加载ggplot2这个包
然后我们再看
它其实就多了一个泛型函数
就对后面这个gg
类标签为gg的这种对象我也能处理了
假如我们一旦有了这么一个意识之后
就发现其实所谓的加号也是一个泛型函数
我们再重新看一下以前的代码
因为我们在迷你案例里面也看到了
前面的管道操作符我们已经理解了
只是一个二元操作符而已
另外还有一个比较奇奇怪怪的是加号
加号前面是ggplot这是一个函数
然后后面这个geom_boxplot
又是另外一个函数调用的过程
我们都说1+1=2这很容易理解
但一个函数加另外一个函数
那就不知道是几个意思
但其实我们现在重新审视完这个加号之后
发现它无非是针对gg的对象
我自己定义的一些操作而已
因为函数就是个操作
那既然它是一个函数
既然是一种操作
我们就没有什么不可理解的
其实我们可以打开这个"+.gg"看一下
它也是一个二元操作符
接受这两个
其实接受这两个参数
这个参数的类就是gg
所以它对这个参数进行相应的操作
在我们那个ggplot2绘图里面就表现为一系列的
接下来做什么操作
有可能是图层的叠加
也有可能是一些坐标操作等等
也有可能是一些坐标操作等等
当然我们可以定义自己的加号
比如说我给定一个 +.onlyFirst
注意了加号后面一个点
表示我对一个onlyFirst这种类
我有一个自己的操作
同样它也是一个二元操作符
接收两个参数
我只将向量a和向量b的第一个元素相加
并且返回
比如说我a现在目前我可以赋值为1:5
那a加6:10的话
它应该结果就是7-9-11-13-15
在正常情况下是这样的
假如我现在将a的类贴成onlyFirst
那这个时候因为加号是一个泛型函数
我针对这个onlyFirst这种数据对象
这个a这个数据对象
它要调用 +.onlyFirst
这么一个函数
就分发到这个函数里面来
毫无疑问结果是只有7了
因为我这个函数只是返回两参数的
这两个向量的第一个元素之和
这是我们那个加号
我们重新审视了一下
相信我们认识完这个二元操作符
认识完这个所谓的泛型函数之后
对于我们前面那个迷你小案例
基本上就不会觉得这个代码再奇奇怪怪了
讲完了这个泛型函数之后
我想再讲函数的最后一个知识点
应该也是相对比较重要的
这时候我又重新将菲波那契摆出来了
还是以这个菲波那契数列来讲
讲什么呢 递归
因为我们在几乎所有教材里面
一旦讲到菲波那契数列的时候
基本上都是用来做
演示递归的过程
我们前面讲这个菲波那契数列的时候
比如说1 1 2 3 5 8
一直往下走
我们都怎么做的
通过循环
从这开始一直往下生长
就是从少到多增长的过程
但其实菲波那契数列
比如说我想知道1 2 3 4 5 6
这个长度为6的菲波那契数列
我想求出来的话
前面我们讲都是通过循环来顺着生长
但是我们可以这个思路其实可以倒过来
比如说我现在想求这个长度为6的
菲波那契数列f(6)
其实可以基于
这个长度为5的菲波那契数列来生成它
倒过来
长度为5的话
可以基于
长度为4的菲波那契数列的生长
然后接着往下转
也就是说倒过来生长也可以
要求长度为6的菲波那契数列
只要能求出长度为5的话
那长度为6的菲波那契数列怎么求
就把长度为5的菲波那契数列最后两个数相加
放在后面就可以
然后长度为5的话怎么求
只要求到长度为4就可以
这样的话就是层层递进 层层递进
这个菲波那契数列其实也可以生长出来
这面其实就是一个递归的方式
来实现这个菲波那契数列
当然讲这个具体如何实现菲波拉契数列之前
通过这个递归方式实现菲波拉契数列
在这讲这个之前
我们先简单温习一下什么叫递归
其实所谓递归即便我们没有学过编程的话
递归的思想其实我们早就在很多故事里面
其实已经有所体现了
一个非常典型的故事
从前有座山
山上有座庙
庙里有个老和尚
老和尚在给小和尚讲故事
故事讲的是
又是从前有座山
山上有座庙
又有个老和尚
老和尚在给小和尚讲故事
故事里面又有山又有庙
又有老和尚
老和尚又在给小和尚讲故事
其实就这样层层递进层层递进
仿佛是无穷无尽的
仿佛是无穷无尽的
这其实就是一个递归的过程
这就是一个递归的过程
当然我们可以通过R代码来实现一下
这个老和尚讲故事的这么递归的过程
我先定义一个函数
叫old_monk_story
表示老和尚讲故事
当然我这里接收一个参数
就第几个老和尚
比如刚开始是第一个老和尚
从前有座山这么第一个老和尚在讲故事
老和尚讲的故事是
这个再从前又个老和尚
那这个时候就是第二个老和尚
这个时候我们接受一个参数
就是它究竟这个递归有几层
递归层级作为我的参数传递过来
具体函数实现是什么呢
就是从前有一个老和尚在讲故事
这个从前有山我就不管它了
就是从前我们到这个从前我们得量化一下
就说我们说从前的时候只是400年前
这都是我们自己定义的
400年前多少
就是2018减掉400乘以我刚才这个参数
假如第二个老和尚的话就是2018减掉400乘以2
从前有个老和尚在讲故事
故事讲的是
再从前又有一个老和尚在讲故事
还调用我这个old_monk_story
所以这就嵌套的递归的
当然因为我们这里面
它其实一旦说到递归的时候
它其实都有一个触底的过程
咱们这个老和尚讲故事怎么触底
因为我们都知道
据说这个佛教是公元66年传到我国的
也就是说从前从前从前的从前
当然可一直往前从前
但这个从前一旦从前到66年之前了
那我认为佛教都没有
老和尚肯定也没有
所以假如我这个2018减掉400乘以我这个相应的
递归的层级
假如它是大于等于66的话
我认为这个故事都有可能成立
否则的话我就不再执行了
那这个时候我就回来
就是一旦讲完了故事之后
我就说这个老和尚的故事讲完了
这里面我们通过R代码实现了一下
这个老和尚讲故事
old_monk_story
那这个结果是
我们一旦执行这个old_monk_story
这个函数的话
我们看一下结果
400年前这个时候其实就是1618年
第一个老和尚在讲故事
故事讲的是
再从前就是1218年
第二个老和尚在讲故事
第二个老和尚讲故事再从前818年
第三个老和尚讲故事
再从前418年第四个老和尚讲故事
418年之前再往前就没有了
418再减400就是公元18年
那这个时候佛教都没有
就没有所谓的老和尚
那这个时候就得往回走了
就层层递进层层递进
然后现在往回走
其实我们可以看得出来
就是第四个老和尚故事讲完了
第三个老和尚故事讲完了
第二个老和尚故事讲完
第一个老和尚故事也讲完了
所以我们可以看得出来
这个递归它其实是层层递进的过程
然后逐层回归
层层《递》进 逐层回《归》
这个递归的含义出来了
它就是一个层层递进再逐层回归的过程
这就是我们所谓的递归的一个最基本的含义
那我们温习完这个递归基本含义之后
我们再来看看
通过递归怎么来实现这个菲波那契数列
我们定义一个函数叫fib
同样我们就是说这个长度为n的话
我就将它作为参数
递归我们刚刚讲有一个触底的过程
当n等于1的时候
我结果有多少就是1
假如是n的大于等于2的话
我就把这个
长度为n-1菲波那契数列拿过来
然后把它的最后两位求和
求和之后放在那后面
就combine一下
所以我们一看这里面其实用到
长度为n减1的菲波那契数列
就我们说倒过来生长的过程
倒过来层层递进的
我们一看
这里面其实就是一个递归的实现方式
说起来也非常简单
这里面我们需要稍微补充一下
我们前面讲这个菲波那契数列的时候
1-1-2-3-5-8等等
我们都是从第三个元素开始
是属于前两个元素之和
但我们这边用了一个讨巧的办法
其实我们除了n==1之外
按理说应该还n==2应该也得做一个触底
也得给一个定义的
但是我们用到tail这个函数
tail这个函数
tail()这个函数表示
取后面这个对象菲波那契数列
长度为n-1的菲波那契数列
最后两位
当我这个fib(1)的时候
也就是说长度为1的菲波那契数列
我交给它的时候
因为这个时候我长度是为1
但是我要取两个出来
取不出来
但对于R来讲
对这个tail这个函数来讲
它会怎么样
它就把这个1本身就返回就可以了
它不会说我要报一个错
本来应该要只有一个元素
我现在从里面取两个取不出来
不会报错
所以我从2开始
从第二个元素开始
其实我就会调用下面这个逻辑
可以通过下面这个逻辑生成
这其实就是通过递归的方式来实现
菲波那契数列
通过递归的方式实现斐波那契数列
好
以上就是我们关于函数的全部内容
本次课到此结束
谢谢大家
-第1章 气象万千、数以等观
--第1章 作业
-第2章 所谓学习、归类而已
--第2章 作业
-第3章 格言联璧话学习
--第3章 作业
-第4章 源于数学、归于工程
--第4章 作业
-讨论题
-第5章 工欲善其事、必先利其器
--第5章 作业
-第6章 基础编程——用别人的包和函数讲述自己的故事
--6.1 编程环境
--6.4 控制流
--第6章 作业
-第7章 数据对象——面向数据对象学习R语言
--第7章 作业
-第8章 人人都爱tidyverse
--第8章 作业
-第9章 最美不过数据框
--第9章 作业
-第10章 观数以形
--第10章 作业
-第11章 相随相伴、谓之关联
--11.1 导引
--第11章 作业
-第12章 既是世间法、自当有分别
--12.1 导引
--第12章 作业
-第13章 方以类聚、物以群分
--13.1 导引
--第13章 作业
-第14章 庐山烟雨浙江潮
--第14章 作业