Python数据结构与算法--面向对象

一个分数类

下面来看一个非常普通的例子,用来展示实现抽象数据类型的一个用户自定义类:Fraction(分数). 我们已经知道 Python 给我们提供了大量的类. 有很多可以适当地帮我们构建分数类型的数据对象.

一个分数比如cd/usr/local/hadoop@Master:/usr/local

 Fraction 类的方法应该能够让 Fraction 对象可以像其他的数值那样进行计算. 我们需要可以进行分数之间的 加, 减, 乘, 和 除 运算. 更进一步, 所有的方法应该返回最简分数.

在 Python中, 我们定义一个类的名字还有一些方法,类似于定义一个函数,例如,

class Fraction:

   #the methods go here

提供了给我们定义方法的框架.第一个方法是所有类都要提供的构造器. 构造函数定义了类创建的方式. 要创建分数对象, 我们需要提供两部分的数据:分子和分母. 在 Python中, 构造函数使用 __init__ (两条下划线包围 init) ,如下所示:

Listing 2

class Fraction:

    def __init__(self,top,bottom):

        self.num = top
        self.den = bottom

注意到参数列表含有三个参数: (selftopbottom). self 是一个引用对象自身的特殊的参数. 它通常作为第一个参数; 但是, 它从不在调用的时候传值. 之前已经讲过,分数包含两部分(分子和分母). 记号 self.num 在构造函数中被定义为 fraction 对象具有一个叫num 的内部数据对象. 同理, self.den 也是类似的目的.

要实现 Fraction 类, 我们需要调用构造函数. 接着通过类名传递参数 (注意到我们从不直接调用__init__). 例如:

myfraction = Fraction(3,5)

创建一个分数对象 myfraction 代表分数3/5 .

接着要做的事情就是给抽象数据类型实现方法. 首先, 意识到当我们要输出一个 Fraction 对象.

>>> myf = Fraction(3,5)
>>> print(myf)
<__main__.Fraction instance at 0x409b1acc>

fraction 对象, myf, 并不知道怎样响应输出操作.  print 函数需要对象转换为可输出的字符串格式,这样才能输出. 唯一的选择 myf 必须显示变量实际的地址引用(自身的地址). 这不是我们想要的.

有两种解决问题的办法. 一种是定义一种称为 show 的方法,可以将Fraction 对象作为一个字符串的形式打印. 我们可以实现如 Listing 3所示.假如我们按照前面讲的创建 Fraction 对象, 我们可以让它输出自身, 换句话说, 打印自身按照适当的格式. 不幸的是, 这通常不起作用. 为了使输出工作正常, 我们必须告诉 Fraction 类怎样将自身转换为字符串的格式.

Listing 3

def show(self):
     print(self.num,"/",self.den)
>>> myf = Fraction(3,5)
>>> myf.show()
3 / 5
>>> print(myf)
<__main__.Fraction instance at 0x40bce9ac>

在 Python, 所有的类都提供但不是都适用的标准的方法. 其中之一, __str__,就是一个将对象转换为字符串的方法. 这个方法默认的实现是用来以字符串格式返回类实例的地址. 我们必须为这个方法提供一个“更好的”实现. 我们说这个新的方法重载前面的, 或者说重新定义了方法的行为.

要实现这个,我们简单地定义一个名叫 __str__ 的方法并给出实现 如Listing 4. 这个定义除了使用特殊参数 self以外不需要其他的信息. 注意函数中的不同的实现办法.

Listing 4

def __str__(self):
    return str(self.num)+"/"+str(self.den)
>>> myf = Fraction(3,5)
>>> print(myf)
3/5
>>> print("I ate", myf, "of the pizza")
I ate 3/5 of the pizza
>>> myf.__str__()
'3/5'
>>> str(myf)
'3/5'
>>>

我们可以为我们的新 Fraction 类覆盖很多其他的方法. 其中一些最重要的是一些基础的算术运算操作. 我们可以创建两种 Fraction 对象,同时使用“+” 符号将它们相加 . 这时, 如果我们使两分数相加, 我们得到:

>>> f1 = Fraction(1,4)
>>> f2 = Fraction(1,2)
>>> f1+f2
Traceback (most recent call last):
  File "<pyshell#173>", line 1, in -toplevel-
    f1+f2
TypeError: unsupported operand type(s) for +:
          'instance' and 'instance'

如果你仔细观察错误信息, 你将发现问题是: “+” 操作符不能理解Fraction 操作.

我们可以通过给 Fraction 类提供重载的加法函数来实现. 在 Python, 这种方法称为 __add__ 同时需要两个参数. 第一个参数, self,  第二个参数是另一个操作数. 例如,

f1.__add__(f2)

当 Fraction  f1 加 Fraction f2. 可以写成标准的形式:f1+f2.

两个分数必须有相同的分母才能直接相加. 使它们分母相同最简单的方法是通分: ,具体实现如 Listing 5. 加法函数返回了一个新的 Fraction 对象.

Listing 5

def __add__(self,otherfraction):

     newnum = self.num * otherfraction.den + self.den*otherfraction.num
     newden = self.den * otherfraction.den

     return Fraction(newnum,newden)
>>> f1=Fraction(1,4)
>>> f2=Fraction(1,2)
>>> f3=f1+f2
>>> print(f3)
6/8
>>>

上面的加法函数看起来实现了我们期望的, 但是还可以更完美. 注意到 6/8 是正确的结果,但是却不是以 “最简项” 的形式展示的. 最好的表达式为3/4. 为了使我们的结果为最简项的形式, 我们需要一个辅助函数才化简分数. 这个函数可以求出最大公约数, 或者称为 GCD. 可以通过分子和分母的最大公约数来达到化简分数的目的.

计算最大公约数最著名的算法要数 Euclid算法,原理我就不详细指明了,很简单。实现如下:

>>> def gcd(m, n):
    while m % n != 0:
        oldm = m
        oldn = n
        m = oldn
        n = oldm % oldn
    return n

>>> print gcd(20, 10)
10

这样我们就可以化简任何的分数了,代码如下: (Listing 6).

Listing 6

def __add__(self,otherfraction):
    newnum = self.num*otherfraction.den + self.den*otherfraction.num
    newden = self.den * otherfraction.den
    common = gcd(newnum,newden)
    return Fraction(newnum//common,newden//common)
>>> f1=Fraction(1,4)
>>> f2=Fraction(1,2)
>>> f3=f1+f2
>>> print(f3)
3/4

我们的 Fraction 对象现在有两个非常重要的方法,如上图所示. 一些需要新增进我们的实例类 Fraction 的方法是:允许两个分数进行比较. 假如我们有两个 Fraction 对象, f1 和f2f1==f2 将得到True 假如他们指向同一个对象. 即使分子分母都相同,但是不满足条件依然将不相等. 这被称为 shallow equality (如下图).

我们可以创建 deep equality (如上图)–通过值相等来判断, 不同于引用–通过覆盖 __eq__ 方法.  __eq__ 是另一个存在于所有类中标准方法. __eq__ 方法比较两个对象当值相等的时候返回 True ,否则返回 False.

在 Fraction 类中, 我们实现了 __eq__ 方法通过常规比较方法来比较分数 (see Listing 7). 值得注意的是还有其他的方法可以覆盖. 例如,  __le__ 方法提供了小于等于功能.

Listing 7

def __eq__(self, other):
    firstnum = self.num * other.den
    secondnum = other.num * self.den

    return firstnum == secondnum

完整的 Fraction 类的代码如下所示:

def gcd(m,n):
    while m%n != 0:
        oldm = m
        oldn = n

        m = oldn
        n = oldm%oldn
    return n

class Fraction:
     def __init__(self,top,bottom):
         self.num = top
         self.den = bottom

     def __str__(self):
         return str(self.num)+"/"+str(self.den)

     def show(self):
         print(self.num,"/",self.den)

     def __add__(self,otherfraction):
         newnum = self.num*otherfraction.den + \
                      self.den*otherfraction.num
         newden = self.den * otherfraction.den
         common = gcd(newnum,newden)
         return Fraction(newnum//common,newden//common)

     def __eq__(self, other):
         firstnum = self.num * other.den
         secondnum = other.num * self.den
         return firstnum == secondnum

x = Fraction(1,2)
y = Fraction(2,3)
print(x+y)
print(x == y)

运行结果:

7/6False
时间: 2024-10-22 19:59:29

Python数据结构与算法--面向对象的相关文章

Python数据结构与算法--List和Dictionaries

Lists 当实现 list 的数据结构的时候Python 的设计者有很多的选择. 每一个选择都有可能影响着 list 操作执行的快慢. 当然他们也试图优化一些不常见的操作. 但是当权衡的时候,它们还是牺牲了不常用的操作的性能来成全常用功能. 本文地址:http://www.cnblogs.com/archimedes/p/python-datastruct-algorithm-list-dictionary.html,转载请注明源地址. 设计者有很多的选择,使他们实现list的数据结构.这些选

Python数据结构与算法--算法分析

一个有趣的问题经常出现,那就是两个看似不同的程序,到底哪个更好呢? 要回答这个问题, 我们必须知道程序和代表程序的算法有很大的区别. 算法是一个通用的, 解决问题的一条条的指令. 提供一个解决任何具有指定输入的实例问题方法, 算法产生期望的结果. 一个程序, 另一方面, 是将算法用某一门编程语言代码实现. 有很多的程序实现的同一算法, 取决于程序员和编程语言的使用. 进一步的探究这种差异, 考察下面的函数代码. 这个函数解决一个简单的问题, 计算前n个自然数的和. 解决方案遍历这 n 个整数,

剪短的python数据结构和算法的书《Data Structures and Algorithms Using Python》

  按书上练习完,就可以知道日常的用处啦 #!/usr/bin/env python # -*- coding: utf-8 -*- # learn <<Problem Solving with Algorithms and Data Structures>> # Release 3.0 # chengang882 @ 2016-12-20 # 它可以检查常用的语法里,({[]})这些符号是否是正常闭合的 # Completed implementation of a stack

《数据结构与算法:Python语言描述》一1.4数据结构

1.4数据结构 从程序输入和输出的角度看,用计算机解决问题,可以看作实现某种信息表示形式的转换.如图1.5所示,把以一种形式表示的信息(输入)送给程序,通过在计算机上运行程序,产生出以另一种形式表示的信息(输出).如果: 具体的"信息表示A"表达了需要求解的某个问题的实例. 得到的"信息表示B"表达了与这个实例对应的求解结果. 那么就可以认为,这个程序完成了该问题实例的求解工作. 为了能用计算机处理与问题有关的信息,就必须采用某种方式表示它,并将相应表示送入计算机.

《数据结构与算法:Python语言描述》一2.3类的定义和使用

2.3类的定义和使用 前面给出了两个有理数类的定义,帮助读者得到一些有关Python类机制的直观认识.本节将介绍Python类定义的进一步情况.本书中对类的使用比较规范,涉及的与Python类定义相关的机制不多,只需要有最基本的了解就可以学习后面内容.另一方面,本书的主题是数据结构和算法,并不计划全面完整地介绍Python语言的面向对象机制和各种使用技术.本节主要想给读者提供一些可参考的基本材料,因此,下面有关Python语言的相关介绍将限制在必要的范围内,供读者参考,不深入讨论.有关Pytho

《数据结构与算法:Python语言描述》一第1章 绪论

第1章 绪论 作为基于Python语言的"数据结构与算法"教程,本章首先讨论一些与数据结构和算法有关的基础问题,还将特别关注Python语言的一些相关情况. 1.1计算机问题求解 使用计算机是为了解决实际问题.计算机具有通用性,其本身的功能很简单,就是能执行程序,按程序的指示完成一系列操作,得到某些结果,或者产生某些效果.要想用计算机处理一个具体问题,就需要有一个解决该问题的程序.经过长期努力,人们已经为各种计算机开发了许多有用的程序.在面对一个需要解决的问题时,如果恰好有一个适用的程

《数据结构与算法:Python语言描述》一3.2顺序表的实现

3.2顺序表的实现 顺序表的基本实现方式很简单:表中元素顺序存放在一片足够大的连续存储区里,首元素(第一个元素)存入存储区的开始位置,其余元素依次顺序存放.元素之间的逻辑顺序关系通过元素在存储区里的物理位置表示(隐式表示元素间的关系). 3.2.1基本实现方式 最常见情况是一个表里保存的元素类型相同,因此存储每个表元素所需的存储量相同,可以在表里等距安排同样大小的存储位置.这种安排可以直接映射到计算机内存和单元,表中任何元素位置的计算非常简单,存取操作可以在O(1) 时间内完成. 设有一个顺序表

Python实现的数据结构与算法之队列详解_python

本文实例讲述了Python实现的数据结构与算法之队列.分享给大家供大家参考.具体分析如下: 一.概述 队列(Queue)是一种先进先出(FIFO)的线性数据结构,插入操作在队尾(rear)进行,删除操作在队首(front)进行. 二.ADT 队列ADT(抽象数据类型)一般提供以下接口: ① Queue() 创建队列 ② enqueue(item) 向队尾插入项 ③ dequeue() 返回队首的项,并从队列中删除该项 ④ empty() 判断队列是否为空 ⑤ size() 返回队列中项的个数 队

Python实现的数据结构与算法之双端队列详解_python

本文实例讲述了Python实现的数据结构与算法之双端队列.分享给大家供大家参考.具体分析如下: 一.概述 双端队列(deque,全名double-ended queue)是一种具有队列和栈性质的线性数据结构.双端队列也拥有两端:队首(front).队尾(rear),但与队列不同的是,插入操作在两端(队首和队尾)都可以进行,删除操作也一样. 二.ADT 双端队列ADT(抽象数据类型)一般提供以下接口: ① Deque() 创建双端队列 ② addFront(item) 向队首插入项 ③ addRe