休假回来发现自己在刷题小组进度滞后,昨晚想着刷几道题赶赶进度,其中有一道还挺有意思:
刚用斐波那契数列的思路分析完,想再分析下附加题中面向对象的方法,恰好 真·对象 发来了信息,并产生了标题中描述的对话。
她:“不懂什么是面向对象”
我:“就是面对着你”
她:“那得先给兔子找上对象,不止是个数学问题”
我:“……”
我:“人家就是一对兔子”
她:“哦,生的还得是一对兄妹,然后近亲结婚是么”
我:“……”
值得表扬的是,今天她还能记起昨天那问题,我也顺便就着例子做了番讲解,毕竟她也不懂编程,只聊了下大致思路。
经过一番讲解,真·对象 表示能理解了、而且非常透彻!
而我此刻立马联想到一张表情图:
好,让我们回到那个生兔子的算法题,同时也正好拿这个题目来说道说道“面向过程”和“面向对象”的概念。
#面向过程
如果没有接触过斐波那契数列,初遇这题会一脸懵。
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。
百度百科-斐波那契数列
有了斐波那契数列概念后,计算兔子数量也就转化成了计算数列中第 n 项的问题。相应地,我们的思路也是如何通过代码计算该数列中的第 n 项,明显的是以计算过程为中心,也就是所谓的“面向过程”。
Python 代码如何实现该过程呢?我们可以定义个函数,n 代表兔子问题中的月份或者兔子数列中的第 n 项,n < 3 时结果是 1,n >= 3 之后就需要通过前两项相加来不断产生后续的结果、故通过一个 for 循环来执行该递推过程。
def get_result(n):
if n<3:
result = 1
else:
x = 1
y = 1
for i in range(n-2):
x,y = y,x+y
result = y
return result
# 获取第 10 个月的兔子数量;获取兔子数列的第 10 项
result = get_result(10)
print(result)
通过运行代码,第 10 个月兔子数量为 55 对。
#面向对象
首先是面向对象的概念:
面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
百度百科-面向对象
正如我们所知,Python 是一门面向对象的编程语言,其中通过 Class 类来描述具有相同的属性和方法的对象的集合,而对象也就是类的实例。
继续拿兔子说事,如果尝试按照时间轴去计算兔子数量其实比较难整理,不妨换个角度,我们把一对兔子封装成一个“类”,这样题目中的每对兔子就都成了这个兔子“类”的实例,而且每一对兔子的属性和功能都是相同的:它们都可以拥有数量的属性,也都可以具有繁殖的功能,唯一的区别就是其“出生时间”不同,也就是对应的时间 n 不同。
将兔子对转化成具体对象后,我们想获取的是兔子数目,那么就可以给兔子实例添加一个变量 count 用来统计数目。对每对兔子这个对象来说,它所关联的数量是它自身 1 对 和所有兔宝宝数量的总和。而其兔宝宝这个对象是每个月都会产生,且该兔宝宝数量又和兔孙孙的数量有关……这样,我们通过定义“类”,变相地规定好了所有兔子对象的行为和属性。
最终我们只要给定时间,便可通过兔子对象的数量属性获取到结果了~
# 面向对象编程解决兔子问题
# python 中通过 class 这个类来定义对象,我们给定义的对象取名 rabbit_pair (兔子对)
class rabbit_pair:
# def 是用来定义这个对象能力的,首先通过 __init__ 给兔子定义数量标签 count 和 繁殖能力 product
def __init__(self,month):
self.count = 1
self.product(month)
# 繁殖能力要具体表明,是和时间 month 相关的
def product(self,month):
# 当 月数大于 2 时,就会繁殖
while month>2:
# 繁殖出的小兔子也是同样的兔子对象,区别是它所经历的时间比父母要少 2 个月,所以时间 -2
child = rabbit_pair(month-2)
# 繁殖后,兔子对象数量标签要把繁殖出的小兔子数量标签数目也给加进来
self.count += child.count
# 通过月数控制,来模拟整个时间轴上兔子对象的繁殖与数量变化
month -= 1
# 把自己定义成经历 10 个月的兔子对象的实例
ted_mia = rabbit_pair(10)
# 获取兔子对象的数量标签
result = ted_mia.count
print(result)
通过运行代码,结果也为 55,附加题搞定~!
但是很遗憾,这个思路表面上是用对象来定义兔子,实际仍是递归思路去计算兔子数量,算不得真·面向对象。
#真·面向对象
原本文章到此是结束了的,但我因为自身编程很少用“类”和面向对象的思路去思考问题,为了保险,专门请教了下 Crossin 先生,还好有这么一问,不然还真的犯错了!
让我们按照正确的面向对象理念重新理一下思路:
仍旧是定义兔子“类”,每一对兔子都是该兔子类的实例。兔子本身并不知道其它兔子数量,所以我们不再引入数量属性。每个兔子随着时间变化自己会年龄增长,年龄达到 3 个月就会繁殖兔崽子。所以,我们要为兔子“类”定义“年龄”属性和“繁殖”的方法;同时,也要定义一个“生长”的方法来控制年龄。(当然,将生长和繁殖定义到同一个方法中更省事)
这样,我们的兔子类便定义好了。接下来要做的是,随着时间变量的变化,我们来来让兔子对象们来生长和繁殖。
至于如何统计数量,我们可以为其建立个“族谱”,也就是所有兔子的列表,只要生成了新的兔子实例,便将其纳入列表中,最终便可以根据该列表长度获取兔子家族的数量了。
修改后的真·面向对象思路代码如下:
# 真·面向对象思路解决兔子问题
# 定义兔子类
class Rabbit:
# 通过 __init__ 构造方法为之后生成的兔子实例添加年龄变量
def __init__(self):
self.age = 0
# 兔子生长通过 grow 方法来控制年龄生长
def grow(self):
self.age += 1
# 兔子生育通过 product 方法来产生新的兔子实例
def product(self):
# 只有年龄大于 2 个月,才可以产生兔崽子
if self.age>2:
return Rabbit()
else:
return None
month = 10
# 为兔子家族建立列表(可以理解为族谱)
family = []
# 第一对兔子是祖先
ted_mia = Rabbit()
# 先将兔祖先纳入族谱
family.append(ted_mia)
# 通过 for 循环控制时间流转
for i in range(month):
# 时间长河中,族谱中的每一对兔子都会生长、繁殖
for member in family:
# 兔子生长
member.grow()
# 兔子繁殖
child = member.product()
# 如果产生了兔崽子,继续纳入族谱
if child:
family.append(child)
# 最终,兔子总数即其族谱中成员个数,也就是列表长度
result = len(family)
print(result)
经过运行,代码结果仍是 55,问题算是圆满解决了。
还好自己在发文之前咨询了下 Crossin 先生,才有了此番修正,也加深了对面向对象编程的理解。
当我将这番修改转发给 真·对象 时,终于,她,蒙圈了。。
从面向真·对象,到真·面向对象解决问题,今天也算收获不小。
以上,感谢阅读~!