![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Como otro ejemplo de un tipo definido por el usuario, definiremos una clase llamada Hora que registra la hora del día. La definición de la clase es como sigue:
class Hora:
pass
Podemos crear un nuevo objeto Hora y asignar atributos para contener las horas, minutos y segundos:
hora = Hora()
hora.horas = 11
hora.minutos = 59
hora.segundos = 30
El diagrama de estado del objeto Hora es así:
A modo de ejercicio, escriba una función imprimeHora que acepte un objeto Hora como argumento y lo imprima en el formato horas:minutos:segundos.
Como un segundo ejercicio, escriba una función booleana despues que tome dos objetos Hora, t1 y t2, como argumentos y devuelva verdadero (1) si t1 sigue cronológicamente a t2 y falso (0) en caso contrario.
En las próximas secciones, escribiremos dos versiones de una función llamada sumaHora que calcule la suma de dos Horas. Mostrarán dos tipos de funciones: funciones puras y modificadores.
Éste es un esbozo de sumaHora:
def sumaHora(t1, t2):
suma = Hora()
suma.horas = t1.horas + t2.horas
suma.minutos = t1.minutos + t2.minutos
suma.segundos = t1.segundos + t2.segundos
return suma
La función crea un nuevo objeto Hora, inicializa sus atributos y devuelve una referencia al nuevo objeto. A esto se le llama función pura porque no modifica ninguno de los objetos que se le pasan y no tiene efectos laterales, como mostrar un valor o tomar una entrada del usuario.
Aquí tiene un ejemplo de cómo usar esta función. Crearemos dos objetos Hora: horaActual, que contiene la hora actual, y horaPan, que contiene la cantidad de tiempo que necesita un panadero para hacer pan. Luego usaremos sumaHora para averiguar cuándo estará hecho el pan. Si aún no ha terminado de escribir imprimeHora, eche un vistazo a la Sección 14.2 antes de probar esto:
>>> horaActual = Hora()
>>> horaActual.horas = 9
>>> horaActual.minutos = 14
>>> horaActual.segundos = 30
>>> horaPan = Hora()
>>> horaPan.horas = 3
>>> horaPan.minutos = 35
>>> horaPan.segundos = 0
>>> horaHecho = sumaHora(horaActual, horaPan)
>>> imprimeHora(horaHecho)
La salida de este programa es 12:49:30, lo que es correcto. Por otra parte, hay casos en los que el resultado no es correcto. ¿Puede imaginar uno?
El problema es que esta función no trata los casos en los que el número de segundos o minutos suma más que sesenta. Cuando ocurre eso, debemos "llevar" los segundos sobrantes a la columna de los minutos o los minutos extras a la columna de las horas.
He aquí una versión corregida de la función:
def sumaHora(t1, t2):
suma = Hora()
suma.horas = t1.horas + t2.horas
suma.minutos = t1.minutos + t2.minutos
suma.segundos = t1.segundos + t2.segundos
if suma.segundos >= 60:
suma.segundos = suma.segundos - 60
suma.minutos = suma.minutos + 1
if suma.minutos >= 60:
suma.minutos = suma.minutos - 60
suma.horas = suma.horas + 1
return suma
Aunque esta función es correcta, empieza a ser grande. Más adelante sugeriremos una aproximación alternativa que nos dará un código más corto.
Hay veces en las que es útil que una función modifique uno o más de los objetos que recibe como parámetros. Normalmente, el llamante conserva una referencia a los objetos que pasa, así que cualquier cambio que la función haga será visible para el llamante. Las funciones que trabajan así se llaman modificadores.
incremento, que añade un número dado de segundos a un objeto Hora, se escribiría de forma natural como un modificador. Un esbozo rápido de la función podría ser éste:
def incremento(hora, segundos):
hora.segundos = hora.segundos + segundos
if hora.segundos >= 60:
hora.segundos = hora.segundos - 60
hora.minutos = hora.minutos + 1
if hora.minutos >= 60:
hora.minutos = hora.minutos - 60
hora.horas = hroa.horas + 1
La primera línea realiza la operación básica, las restantes tratan con los casos especiales que vimos antes.
¿Es correcta esta función? ¿Qué ocurre si el parámetro segundos es mucho mayor que sesenta? En tal caso, no es suficiente con acarrear una vez; debemos seguir haciéndolo hasta que segundos sea menor que sesenta. Una solución es sustituir las sentencias if por sentencias while:
def incremento(hora, segundos):
hora.segundos = hora.segundos + segundos
while hora.segundos >= 60:
hora.segundos = hora.segundos - 60
hora.minutos = hora.minutos + 1
while hora.minutos >= 60:
hora.minutos = hora.minutos - 60
hora.horas = hroa.horas + 1
Ahora esta función es correcta, pero no es la solución más eficiente.
Como ejercicio, reescriba esta función de modo que no contenga tantos bucles.
Como un segundo ejercicio, reescriba incremento como una función pura, y escriba una función que llame a ambas versiones.
Todo lo que se pueda hacer con modificadores puede hacerse también con funciones puras. En realidad, algunos lenguajes de programación sólo permiten funciones puras. Hay ciertas evidencias de que los programas que usan funciones puras son más rápidos de desarrollar y menos propensos a los errores que los programas que usan modificadores. Sin embargo, a veces los modificadores son útiles, y en algunos casos los programas funcionales son menos eficientes.
En general, recomendamos que escriba funciones puras siempre que sea razonable hacerlo así y recurra a los modificadores sólo si hay una ventaja convincente. Este enfoque podría llamarse estilo funcional de programación.
En este capítulo mostramos una aproximación al desarrollo de programas a la que llamamos desarrollo de prototipos. En cada caso, escribimos un esbozo basto (o prototipo) que realizaba el cálculo básico y luego lo probamos sobre unos cuantos casos, corrigiendo los fallos tal como los encontrábamos.
Aunque este enfoque puede ser efecitvo, puede conducirnos a código que es innecesariamente complicado, ya que trata con muchos casos especiales, y poco fiable, porque es difícil saber si encontró todos los errores.
Una alternativa es el desarrollo planificado, en el que una comprensión del problema en profundidad puede hacer la programación mucho más fácil. En este caso, el enfoque es que un objeto Hora es en realidad ¡un número de tres dígitos en base 60! El componente segundo es la "columna de unidades", el componente minuto es la "columna de las sesententas" y el componente hora es la "columna de las tresmilseiscentenas".
Cuando escribimos sumaHora e incremento, en realidad estábamos haciendo una suma en base 60, que es por lo que debíamos acarrear de una columna a la siguiente.
Esta observación sugiere otro enfoque para el problema. Podemos convertir un objeto Hora en un simple número y sacar provecho del hecho de que la máquina sabe la aritmética necesaria. La siguiente función convierte un objeto Hora en un entero:
def convierteASegundos(t):
minutos = t.horas * 60 + t.minutos
segundos = minutos * 60 + t.segundos
return segundos
Ahora, sólo necesitamos una forma de convertir un entero en un objeto Hora:
def haceHora(segundos):
hora = Hora()
hora.horas = segundos/3600
segundos = segundos - hora.horas * 3600
hora.minutos = segundos/60
segundos = segundos - hora.minutos * 60
hora.segundos = segundos
return hora
Puede que tenga usted que pensar un poco para convencerse de que esta técnica para convertir de una base a otra es correcta. Suponiendo que está usted convencido, puede usar estas funciones para reescribir sumaHora:
def sumaHora(t1, t2):
segundos = convierteASegundos(t1) + convierteASegundos(t2)
return haceHora(segundos)
Esta versión es mucho más corta que la original, y es mucho más fácil de demostrar que es correcta (suponiendo, como es habitual, que las funciones a las que llama son correctas).
Como ejercicio, reescriba incremento de la misma forma.
De algún modo, convertir de base 60 a base 10 y de vuelta es más difícil que simplemente manejarse con las horas. La conversión de base es más abstracta; nuestra intuición para tratar con las horas es mejor.
Pero si tenemos la comprensión para tratar las horas como números en base 60, y hacer la inversión de escribir las funciones de conversión (convierteASegundos y haceHora), obtenemos un programa que es más corto, más fácil de leer y depurar y más fiable.
También es más fácil añadir funcionalidades más tarde. Por ejemplo, imagine restar dos Horas para hallar el intervalo entre ellas. La aproximación ingenua sería implementar la resta con acarreo. Con el uso de las funciones de conversión será más fácil y con mayor probabilidad, correcto.
Irónicamente, a veces hacer un poblema más complejo (o más general) lo hace más fácil (porque hay menos casos especiales y menos oportunidades de error).
Cuando escribe una solución general para una clase de problemas, en contraste con una solución específica a un problema concreto, ha escrito un algoritmo. Mencionamos esta palabra antes pero no la definimos con precisión. No es fácil de definir, así que probaremos un par de enfoques.
Primero, piense en algo que no es un algoritmo. Cuando usted aprendió a multiplicar números de una cifra, probablemente memorizó la tabla de multiplicar. En efecto, memorizó 100 soluciones específicas. Ese tipo de conocimiento no es algorítmico.
Pero si usted era "haragán" probablemente hizo trampa aprendiendo algunos trucos. Por ejemplo, para encontrar el producto de n por 9, puede escribir n-1 como el primer dígito y 10-n como el segundo dígito. Este truco es una solución general para multiplicar cualquier número de una cifra por 9. ¡Eso es un algoritmo!
De forma similar, las técnicas que aprendió para la suma y la resta con acarreo y la división larga son todas algoritmos. Una de las características de los algoritmos es que no requieren inteligencia para llevarse a cabo. Son procesos mecánicos en los que cada paso sigue al anterior de acuerdo a un conjunto simple de reglas.
En nuestra opinión, es un poco vergonzoso que los humanos pasen tanto tiempo en la escuela aprendiendo a ejecutar algoritmos que, de forma bastante similar, no exigen inteligencia.
Por otra parte, el proceso de diseñar algoritmos es interesante, un desafío intelectual y una parte primordial de lo que llamamos programar.
Algunas de las cosas que la gente hace naturalmente, sin dificultad ni pensamiento consciente, son las más difíciles de expresar algorítmicamente. Entender el lenguaje natural es un buen ejemplo. Todos lo hacemos, pero hasta el momento nadie ha sido capaz de explicar cómo lo hacemos, al menos no en la forma de un algoritmo.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |