Fracciones de Peano
Las últimas 2 semanas estuve haciendo en el laburo el curso de diseño orientado a objetos que da Hernán Wilkinson de 10 Pines. Dentro del curso, hay un ejercicio para hacer single dispatch que consiste en modelar números enteros y fracciones.
Como usar los números que me da el lenguaje para modelar los números me pareció choto, hice una implementación de los números de Peano y con eso hice las fracciones.
Abajo les dejo la primera versión que pasa los tests, pero que es media fea porque tiene ifs de la forma if isinstance(sumando, Enteros): #código. Si miran al final, van a ver los tests de unidad que tiene que pasar el código. Antes está la implementación.
import unittest
class Numero:
DESCRIPCION_DE_ERROR_DE_DIVISION_POR_CERO = 'No se puede dividir por 0'
def esCero(self):
self.shouldBeImplementedBySubclass()
def esUno(self):
self.shouldBeImplementedBySubclass()
def __add__(self,sumando):
self.shouldBeImplementedBySubclass()
def __mul__(self,factor):
self.shouldBeImplementedBySubclass()
def __div__(self,divisor):
self.shouldBeImplementedBySubclass()
def shouldBeImplementedBySubclass(self):
raise NotImplementedError('Should be implemented by the subclass')
def __le__(self, other):
return self < other or self == other
class Entero(Numero):
pass
class Zero(Entero):
def esCero(self): return True
def esUno(self): return False
def __eq__(self, other):
return self.__class__ == other.__class__
def __add__(self,sumando):
return sumando
def __mul__(self, factor):
return self
def __repr__(self): return "0"
def __lt__(self, other): return not self == other
class Next(Entero):
def __init__(self, before):
self.before = before
def esCero(self): return False
def esUno(self): return self.before.esCero()
def __add__(self,sumando):
if isinstance(sumando, Entero):
return Next(self.before + sumando)
return sumando + self
def __mul__(self, factor):
return factor + self.before * factor
def __repr__(self): return "N" + repr(self.before)
def __eq__(self, other):
if isinstance(other, Fraccion):
return other == self
if self.__class__ != other.__class__: return False
return self.before == other.before
def __lt__(self, other):
if self.__class__ != other.__class__: return False
return self.before < other.before
def __div__(self, other):
if isinstance(other, self.__class__):
return Fraccion(self, other)
if isinstance(other, Fraccion):
return Fraccion(other.d * self, other.n)
raise Exception("No se puede dividir por %r" % other)
class Fraccion(Numero):
def __init__(self, n, d):
if (d.esCero()):
raise Exception(Numero.DESCRIPCION_DE_ERROR_DE_DIVISION_POR_CERO)
self.n = n
self.d = d
def __add__(self, other):
#a/b + c/d = (a.d + c.b) / (b.d)
if isinstance(other, self.__class__):
return Fraccion((self.n * other.d + other.n * self.d), self.d * other.d)
if isinstance(other, Entero):
return Fraccion(self.n + other * self.d, self.d)
raise Exception("No se sumar %r" % other)
def __mul__(self, other):
# (a/b) * (c/d) = (a.c) / (b.d)
if isinstance(other, self.__class__):
return Fraccion(self.n * other.n, self.d * other.d)
if isinstance(other, Entero):
return Fraccion(self.n * other, self.d)
def __div__(self, other):
# (a/b) / (c/d) = (a.d) / (b.c)
if isinstance(other, self.__class__):
return Fraccion(self.n * other.d, other.n * self.d)
if isinstance(other, Entero):
return Fraccion(self.n, self.d * other)
raise Exception("No se dividir %r" % other)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.n * other.d == self.d * other.n
if isinstance(other, Entero):
return self.n == self.d * other
return False
def __repr__(self): return repr(self.n) + "/" + repr(self.d)
class NumeroTest(unittest.TestCase):
def createCero(self):
return Zero()
def createUno(self):
return Next(self.createCero())
def createDos(self):
return Next(self.createUno())
def createTres(self):
return Next(self.createDos())
def createCuatro(self):
return Next(self.createTres())
def createCinco(self):
return Next(self.createCuatro())
def createSeis(self):
return Next(self.createCinco())
def createSiete(self):
return Next(self.createSeis())
def createUnQuinto(self):
return Fraccion(self.uno, self.cinco)
def createDosQuintos(self):
return Fraccion(self.dos, self.cinco)
def createDosVeinticincoavos(self):
return Fraccion(self.dos, self.cinco * self.cinco)
def createUnMedio(self):
return Fraccion(self.uno, self.dos)
def createCincoMedios(self):
return Fraccion(self.cinco, self.dos)
def createSeisQuintos(self):
return Fraccion(self.seis, self.cinco)
def createCuatroMedios(self):
return Fraccion(self.cuatro, self.dos)
def createDosCuartos(self):
return Fraccion(self.dos, self.cuatro)
def setUp(self):
self.cero = self.createCero()
self.uno = self.createUno()
self.dos = self.createDos()
self.tres = self.createTres()
self.cuatro = self.createCuatro()
self.cinco = self.createCinco()
self.seis = self.createSeis()
self.siete = self.createSiete()
self.unQuinto = self.createUnQuinto()
self.dosQuintos = self.createDosQuintos()
self.dosVeinticincoavos = self.createDosVeinticincoavos()
self.unMedio = self.createUnMedio()
self.cincoMedios = self.createCincoMedios()
self.seisQuintos = self.createSeisQuintos()
self.cuatroMedios = self.createCuatroMedios()
self.dosCuartos = self.createDosCuartos()
def testAEsCeroDevuelveTrueSoloParaElCero(self):
self.assertTrue (self.cero.esCero())
self.assertFalse (self.uno.esCero())
def testBEsUnoDevuelveTrueSoloParaElUno(self):
self.assertTrue (self.uno.esUno())
self.assertFalse (self.cero.esUno())
def testCSumaDeEnteros(self):
self.assertEqual (self.dos,self.uno+self.uno)
def testDMultiplicacionDeEnteros(self):
self.assertEqual(self.cuatro, self.dos*self.dos)
def testEDivisionDeEnteros(self):
self.assertEqual(self.uno, self.dos/self.dos)
def testFSumaDeFracciones(self):
sieteDecimos = Fraccion( self.siete, self.dos * self.cinco ) # <- br="" corresponda="" lo="" por="" que="" reemplazar="">
self.assertEqual (sieteDecimos,self.unQuinto+self.unMedio)
#
# La suma de fracciones es:
#
# a/b + c/d = (a.d + c.b) / (b.d)
#
# SI ESTAN PENSANDO EN LA REDUCCION DE FRACCIONES NO SE PREOCUPEN!
# NO SE ESTA TESTEANDO ESE CASO
#
def testGMultiplicacionDeFracciones(self):
self.assertEqual (self.dosVeinticincoavos,self.unQuinto*self.dosQuintos)
#
# La multiplicacion de fracciones es:
#
# (a/b) * (c/d) = (a.c) / (b.d)
#
# SI ESTAN PENSANDO EN LA REDUCCION DE FRACCIONES NO SE PREOCUPEN!
# TODAVIA NO SE ESTA TESTEANDO ESE CASO
#
def testHDivisionDeFracciones(self):
self.assertEqual (self.cincoMedios,self.unMedio/self.unQuinto)
#
# La division de fracciones es:
#
# (a/b) / (c/d) = (a.d) / (b.c)
#
# SI ESTAN PENSANDO EN LA REDUCCION DE FRACCIONES NO SE PREOCUPEN!
# TODAVIA NO SE ESTA TESTEANDO ESE CASO
#
#
# Ahora empieza lo lindo! - Primero hacemos que se puedan sumar enteros con fracciones
# y fracciones con enteros
#
def testISumaDeEnteroYFraccion(self):
self.assertEqual (self.seisQuintos,self.uno+self.unQuinto)
def testJSumaDeFraccionYEntero(self):
self.assertEqual (self.seisQuintos,self.unQuinto+self.uno)
#
# Hacemos lo mismo para la multipliacion
#
def testKMultiplicacionDeEnteroPorFraccion(self):
self.assertEqual(self.dosQuintos,self.dos*self.unQuinto)
def testLMultiplicacionDeFraccionPorEntero(self):
self.assertEqual(self.dosQuintos,self.unQuinto*self.dos)
#
# Hacemos lo mismo para la division
#
def testMDivisionDeEnteroPorFraccion(self):
self.assertEqual(self.cincoMedios,self.uno/self.dosQuintos)
def testNDivisionDeFraccionPorEntero(self):
self.assertEqual(self.dosVeinticincoavos,self.dosQuintos/self.cinco)
#
# Ahora si empezamos con problemas de reduccion de fracciones
#
def testOUnaFraccionPuedeSerIgualAUnEntero(self):
self.assertEquals(self.dos,self.cuatroMedios)
def testPLasFraccionesAparentesSonIguales(self):
self.assertEquals(self.unMedio,self.dosCuartos)
#
# Las fracciones se reducen utilizando el maximo comun divisor (mcd)
# Por lo tanto, para a/b, sea c = mcd (a,b) => a/b reducida es:
# (a/c) / (b/c).
#
# Por ejemplo: a/b = 2/4 entonces c = 2. Por lo tanto 2/4 reducida es:
# (2/2) / (4/2) = 1/2
#
# Para obtener el mcd pueden usar el algoritmo de Euclides que es:
#
# mcd (a,b) =
# si b = 0 --> a
# si b != 0 -->mcd(b, restoDeDividir(a,b))
#
# Ejemplo:
# mcd(2,4) ->
# mcd(4,restoDeDividir(2,4)) ->
# mcd(4,2) ->
# mcd(2,restoDeDividir(4,2)) ->
# mcd(2,0) ->
# 2
#
def testQLaSumaDeFraccionesPuedeDarEntero(self):
self.assertEquals (self.uno,self.unMedio+self.unMedio)
def testRLaMultiplicacionDeFraccionesPuedeDarEntero(self):
self.assertEquals(self.dos,self.cuatro*self.unMedio)
def testSLaDivisionDeEnterosPuedeDarFraccion(self):
self.assertEquals(self.unMedio, self.dos/self.cuatro)
def testTLaDivisionDeFraccionesPuedeDarEntero(self):
self.assertEquals(self.uno, self.unMedio/self.unMedio)
def testUNoSePuedeDividirEnteroPorCero(self):
try:
self.uno/self.cero
self.fail()
except Exception as e:
self.assertEquals(self.descripcionDeErrorDeNoSePuedeDividirPorCero(),e.message)
def testVNoSePuedeDividirFraccionPorCero(self):
try:
self.unQuinto/self.cero
self.fail()
except Exception as e:
self.assertEquals(self.descripcionDeErrorDeNoSePuedeDividirPorCero(),e.message)
# Este test puede ser redundante dependiendo de la implementacion realizada
def testWNoSePuedeCrearFraccionConDenominadorCero(self):
try:
self.crearFraccionCon(self.uno,self.cero)
self.fail()
except Exception as e:
self.assertEquals(self.descripcionDeErrorDeNoSePuedeDividirPorCero(),e.message)
def crearFraccionCon(self, numerador, denominador):
return Fraccion(numerador, denominador)
def descripcionDeErrorDeNoSePuedeDividirPorCero(self):
return Numero.DESCRIPCION_DE_ERROR_DE_DIVISION_POR_CERO->
En futuros posts voy a ir factorizando este código para que quede más lindo.
Happy hacking,
Aureliano.