Ficha 06 (2018) - Estructuras Repetitivas - Ciclo While (Python) PDF
Ficha 06 (2018) - Estructuras Repetitivas - Ciclo While (Python) PDF
Ficha 06 (2018) - Estructuras Repetitivas - Ciclo While (Python) PDF
Ficha 6
1
Si de repetir acciones se trata, no podemos menos que referir la fantástica película Groundhog Day (en
español conocida como Hechizo del Tiempo) del año 1993, dirigida por Harold Ramis y protagonizada por Bill
Murray y Andie MacDowell. Un locutor de televisión engreído y cascarrabias fue condenado a revivir una y otra
vez el mismo día que más odiaba, sin que lo sepa nadie aparte de él mismo, y sólo pudo escapar a ese castigo
cuando finalmente cambió y se convirtió en una buena persona… después de miles y miles de repeticiones de
la misma jornada y los mismos eventos. Impecable. Extraordinaria. Quizás lo mejor de Bill Murray.
El bloque de acciones o cuerpo del ciclo, que es el conjunto de instrucciones cuya ejecución se
pide repetir.
La cabecera del ciclo, que incluye una condición de control y/o elementos adicionales en
base a los que se determina si el ciclo continúa o se detiene.
Las instrucciones repetitivas o ciclos son implementados por los diversos lenguajes en
formas que varían ligeramente de un lenguaje a otro. El lenguaje Python implementa dos
tipos de ciclos mediante instrucciones específicas y muy flexibles, designándolos
respectivamente como ciclo for y como ciclo while [2]. En esta Ficha 6 analizaremos la forma
de uso y aplicación del ciclo while, y en la Ficha 7 veremos la forma de trabajo y aplicación
del ciclo for.
2.] El ciclo while en Python.
Hemos sugerido que en muchas situaciones necesitaremos desarrollar programas que
ejecuten en forma repetida un conjunto de instrucciones. Tomemos como ejemplo el mismo
programa básico que hemos mostrado en la Ficha 5 para introducir el tema de las variables
de conteo, en el que se cargaban tres números y se pedía determinar cuántos números
negativos se ingresaron. El programa básico que se mostró en la Ficha 5 era el siguiente:
Figura 1: Uso básico de un contador (tomado de la Ficha 5).
__author__ = 'Catedra de AED'
a = 0
num = int(input('Ingrese un número: '))
if num < 0:
a = a + 1
Si bien el programa citado funciona perfectamente bien, encierra dos grandes limitaciones:
1. ¿Qué pasaría si en lugar de sólo tres números tuviéramos que cargar mil números?
Resultaría muy poco práctico desarrollar un programa con mil instrucciones de
lectura y mil instrucciones condicionales. ¿Y si en lugar de mil números fueran diez
mil, o cien mil, o un millón?
2. ¿Qué pasaría además, si no supiésemos la cantidad exacta de números que se deben
cargar al momento de desarrollar el programa? Por ejemplo, es común que el
enunciado o requerimiento no exprese cuántos números vendrán y en cambio
indique que se carguen números hasta que en algún momento se ingrese el cero.
Intente resolver este problema solamente en base a estructuras secuenciales y
condicionales y descubrirá que no es posible.
Las instrucciones repetitivas o ciclos están previstas para que el programador pueda resolver
situaciones como estas con sencillez. Como dijimos, Python provee dos tipos de ciclos: el
ciclo while y el ciclo for.
El programador no debe escribir ninguna instrucción especial dentro del bloque de acciones
para indicar que el ciclo debe volver a la cabecera y evaluar nuevamente la expresión lógica:
ese retorno es automático e implícito.
La lógica esencial de funcionamiento de un ciclo while se refleja muy bien en la forma clásica
de graficarlo en un diagrama de flujo (ver Figura 2). La expresión lógica conforma la
cabecera del ciclo, y su valor determina si el ciclo continúa ejecutando el bloque de acciones,
o se detiene. En la primera oportunidad que esa expresión lógica sea falsa, el ciclo se
detendrá y el programa continuará ejecutando las instrucciones que figuren a la salida del
ciclo. Pero si la expresión lógica es verdadera, ejecutará el bloque y luego automáticamente
volverá a chequear la expresión lógica de la cabecera, repitiendo el esquema.
instrucciones previas
al ciclo
expresión Falso
lógica
Verdadero
bloque o cuerpo de
acciones del ciclo
instrucciones a la
salida del ciclo
Note especialmente que si al evaluar la expresión lógica por primera vez esta fuese falsa,
entonces el bloque de acciones no sería ejecutado y el programa continuaría con las
instrucciones a la salida del ciclo. Debido a este comportamiento, se dice que el ciclo while es
un ciclo de tipo [0, N]: el bloque de acciones puede llegar a ejecutarse entre 0 y N veces,
siendo N un número entero positivo normalmente desconocido (pero que indica un número
finito de repeticiones) [1].
Si bien la forma clásica de graficar un ciclo while (en rigor, un ciclo [0, N]) es muy clara en
cuanto a su lógica, se puede plantear alternativamente un gráfico basado en otro símbolo no
estándar pero específico para el planteo de ciclos, como es el siguiente:
Figura 3: Símbolo alternativo (no estándar) para graficar un ciclo en un diagrama de flujo.
instrucciones
previas al ciclo
Verdadero
expresión lógica
instrucciones a la
salida del ciclo
Inicio
a=0
num
num != 0
num < 0
a += 1
num
Fin
En base a este diagrama mostramos ahora el programa completo para el conteo de los
números negativos, replanteado para usar un ciclo while, sin conocer cuántos números
vendrán pero sabiendo que al ingresar un 0 la carga debe terminar:
2
Y algo similar ocurriría en otros lenguajes en los que el ciclo for está diseñado para desplegar una cantidad
exacta de repeticiones, como en el lenguaje Pascal. Sin embargo, note que en otros lenguajes (como C, C++ o
Java) la instrucción for se implementa en forma tan amplia, que no hay problema en aplicarla en este tipo de
situaciones y en cualquier otra que requiera simplemente un ciclo.
a = 0
num = int(input('Ingrese un numero (o cero para terminar): '))
while num != 0:
if num < 0:
a += 1
num = int(input('Ingrese otro numero (o cero para terminar): '))
El programa utiliza un ciclo while para la carga y procesamiento de los números. Por otra
parte, puede notarse que se carga un valor de num antes de comenzar el ciclo, y otro valor
de num inmediatamente antes de finalizar el bloque de acciones. La primera lectura se
realiza porque de otro modo la variable num podría no existir o podría tener un valor
residual cuando se verifique la expresión lógica de control del ciclo while por primera vez, y
podría esa verificación dar resultados no deseados. Cuando en el ciclo se pregunta si num
es diferente de cero, num debe existir y tener siempre un valor controlado por el
programador. La segunda lectura, efectuada dentro del bloque de acciones al final del
mismo, se efectúa porque en caso de no hacerla la variable num seguiría teniendo el mismo
valor que se cargó en la primera lectura, y eso provocaría que la condición de control se
vuelva a evaluar en verdadero, comenzando una serie infinita de repeticiones para el mismo
valor.
En un ciclo bien planteado, el valor de la variable o variables que se usan en la expresión
lógica de control debe cambiar adecuadamente dentro del bloque de acciones, pues de otro
modo, si el valor nunca cambia, el ciclo repetirá una y otra vez, entrando en una situación de
ciclo infinito. El esquema anterior, que incluye dos lecturas para evitar los problemas
descriptos, es de uso muy común cuando se procesan conjuntos de valores usando un ciclo
while, sin conocer cuántos valores vendrán, y se designa como proceso de carga por doble
lectura [1].
Debe el programador tener especial cuidado cuando usa ciclos, para evitar caer en ciclos
infinitos. Si en el ejemplo anterior se elimina la segunda lectura, se cae irremisiblemente en
un ciclo infinito. Por otra parte, el ciclo infinito podría provocarse en situaciones donde no
haya necesariamente lectura por teclado de la variable de control. Por ejemplo, el siguiente
ciclo está mal planteado, debido a que la variable c empieza valiendo uno (por asignación),
pero nunca cambia luego de valor:
c = 1
while c != 0:
x = int(input('Ingrese un valor: '))
En este caso se produce un error muy común: se usa una variable en la condición de control,
pero dentro del bloque de acciones del ciclo se cambia el valor de otra (u otras) variable/s.
Este segmento de programa provocará que aparezca en pantalla un mensaje solicitando
cargar un valor, se cargará ese valor, y luego se pedirá otro, y luego otro más, y así
sucesivamente, sin terminar nunca el programa.
¿Qué hacer si un programa entra en un ciclo infinito? La manera de interrumpir un programa
(ya sea porque por alguna razón no pueda finalizar normalmente o simplemente porque el
programador desea interrumpirlo), suele consistir en una combinación de teclas que fuerza
al programa a finalizar. En nuestro caso, si el programa se ejecuta desde PyCharm, la
combinación de teclas a usar es: <Ctrl> + <F2>. Es decir, se presiona la tecla Control
(ubicada normalmente a los lados de la barra espaciadora del teclado), y sin soltarla se
presiona también la tecla F2 (la tecla de Función 2, por lo general ubicada en la primera fila
de arriba del teclado). Esta operación suele funcionar sin problemas en PyCharm,
cancelando el programa. Por cierto, es siempre una buena idea grabar los programas antes
de intentar ejecutarlos...
En algunas ocasiones si el programa entra en ciclo infinito o pareciera estar clavado
(aparentemente funcionando pero sin respuesta alguna al usuario) y nada funciona para
detenerlo, no queda otro remedio que intentar interrumpirlo desde el sistema operativo
(usando el administrador de tareas o similar) o incluso apagar el equipo y volverlo a
encender, pero esta operación implica que si el programa no fue grabado, se perderá todo lo
que el programador haya escrito. En PyCharm también se puede intentar interrumpir el
programa usando el mouse: a la izquierda del panel de salida de PyCharm, apunte con el
mouse al pequeño ícono en forma de cuadrado de color rojo, y haga click sobre el. Esto
equivale a pulsar la combinación <Ctrl> + <F2> y el programa en curso se interrumpirá:
Figura 6: Interrupción de un programa en PyCharm.
click aquí...
Por supuesto, podemos utilizar un ciclo while para resolver problemas donde sí se conoce de
antemano la cantidad de iteraciones a realizar, tal como lo hubiéramos hecho con un ciclo
for, aunque al costo de un poco más de trabajo ya que, como veremos, el ciclo for se plantea
de forma más automática mientras que en el ciclo while tendremos que hacer en forma
manual el conteo de vueltas.
Por ejemplo, el siguiente script Python usa un ciclo while para mostrar los primeros diez
múltiplos de un número x que se carga por teclado:
x = int(input("Ingrese un número: "))
i = 1
while i <= 10:
print(x, " * ", i, " = ", x*i)
i += 1
En este caso, la variable i es un contador que sirve para contar la cantidad de vueltas del
ciclo y a la vez para realizar el cálculo de cada multiplicación. Antes de que comience el ciclo
se le da el valor 1 con lo que la expresión lógica de control del ciclo entrega un valor
verdadero y se ejecuta por primera vez el bloque de acciones del ciclo. En la primera
Problema 15.) Cargar por teclado una lista de números enteros que irán llegando uno a uno, y que
representan cantidades mensuales de automóviles nuevos vendidos en el país durante cierto período.
Para indicar que la carga de datos debe finalizar, se ingresará el valor -1 (menos uno) (note que el
valor 0 (cero) puede ser un dato valido: es perfectamente posible que no haya habido ventas en un
mes determinado). Se pide:
a. Determinar cuántas de esas cantidades fueron mayores o iguales que 0 pero menores que
10000 unidades, cuántas fueron mayores o iguales a 10000 pero menores que 15000, y
cuántas fueron mayores o iguales que 15000.
b. Determinar la cantidad total de automóviles nuevos que se vendieron en el país.
c. Determinar si se ingresó al menos una cantidad igual a 0 o no. Informe con un mensaje simple
en pantalla.
Discusión y solución: Este es un problema típico de procesamiento de una sucesión de
números enteros con fines estadísticos. Como no sabemos cuántos números entrarán como
dato, pero sabemos que al cargar un -1 se debe terminar el proceso, entonces podemos usar
un ciclo while y un esquema de carga por doble lectura.
El punto a) solicita contar cuántas cantidades hay en cada uno de tres posibles intervalos de
valores, y para eso necesitaremos tres contadores (que llamaremos c1, c2 y c3).
El punto b) en última instancia pide acumular todos los números cargados y por eso en cada
vuelta usaremos un acumulador (que llamaremos t) para ir sumando todos los números que
se ingresen.
Y para cumplir con el punto c), usaremos una bandera (que llamaremos ok) que nos
permitirá dejar marcado si se cargó al menos una cantidad igual a 0.
El programa completo se muestra a continuación, y luego del mismo haremos algunas
observaciones:
__author__ = 'Catedra de Algoritmos y Estructuras de Datos'
La lógica general es bastante directa: a través del ciclo while se procede a cargar por teclado
los valores de las cantidades vendidas usando para ello la variable cant y un mecanismo de
doble lectura. El ciclo controla que el número ingresado en cant sea diferente de -1 (que es
el valor de corte). Dentro del ciclo, cada valor de cant se controla con un esquema de
condiciones anidadas para saber en qué intervalo se encuentra ese valor y así poder contarlo
en alguno de los tres contadores c1, c2 o c3. Inmediatamente luego el valor de cant
simplemente se suma en el acumulador t para ir obteniendo así el total general de vehículos
vendidos:
# inicialización de contadores, acumuladores y banderas...
c1, c2, c3, t = 0, 0, 0, 0
ok = False
c2 += 1
elif cant >= 15000:
c3 += 1
Para determinar si alguno de los valores cargado en cant en alguna de las vueltas del ciclo
fue igual a cero, se usa una bandera que hemos llamado ok. Antes del ciclo, esa bandera se
inicializa con el valor False, para indicar que hasta ese momento no se ha detectado la carga
de un valor igual a 0 en cant. Si en alguna iteración del ciclo se detecta la carga de un 0 en
cant, el valor del flag ok se vuelve a True y se lo deja en ese estado hasta el final:
ok = False
# ...
Al finalizar el ciclo (por la carga del valor -1) se muestran los valores de los tres contadores y
el del acumulador (note la llamada a print() sin pasarle parámetros, que se hace para
simplemente dejar una línea en blanco en la salida):
# visualización de resultados... punto a)
print()
print('Cantidad de valores >= 0 pero < 10000:', c1)
print('Cantidad de valores >= 10000 pero < 15000:', c2)
print('Cantidad de valores >= 15000:', c3)
Finalmente, para se controla el valor final del flag ok: si ese valor es True, significa que al
menos una vez se cargó el valor 0 en la variable cant durante el ciclo. Pero si el valor de ok es
False significa que ok mantuvo el valor inicial que se le asignó al comenzar el programa y por
lo tanto, nunca se cargó un cero:
# visualización de resultados... punto c)
if ok:
print('Se registró al menos una cantidad de ventas igual cero')
else:
print('No se registró ninguna cantidad de ventas igual cero')
Veamos ahora otro ejemplo de aplicación en el que también se procesa una secuencia
números, pero algo más complejo:
Problema 16.) Cargar por teclado un conjunto de números enteros, uno a uno. La carga sólo debe
terminar cuando se ingrese el cero. Determine si los números que se ingresaron eran todos positivos
o todos negativos (sin importar en qué orden hayan entrado). Por ejemplo, la secuencia {8, 4, 3, 7}
cumple con la consigna indicada (son todos positivos). La secuencia {-2, -1, -5} también cumple (son
todos negativos), pero esta otra secuencia {10, -8, 2, 12} no cumple (hay números de distinto signo
entremezclados). Si todos los números eran efectivamente del mismo signo, muestre también la suma
de esos números (no mostrar la suma si había números de signos diferentes entremezclados).
# cargar el primero...
actual = int(input('Cargue un número entero (con 0 corta): '))
while actual != 0:
# cargar el siguiente...
actual = int(input('Cargue otro (con 0 corta): '))
Para controlar si los números son o no del mismo signo, podemos aplicar una técnica muy
común entre los programadores para dejar algún tipo de señal o aviso lógico, que le indique
al programador si ha ocurrido o no algún suceso en particular.
Procederemos para eso de la siguiente manera: sea la variable booleana ok que
comenzaremos asignando con el valor True antes de comenzar el ciclo de carga. La idea es
que el valor de esa variable indique lo que hasta ese momento se sabe respecto de los signos
de los números cargados: usamos el valor True para registrar que hasta ese momento, todos
los números que se hayan visto eran efectivamente del mismo signo (lo cual es
efectivamente así si sólo se cargó uno). Si a lo largo del ciclo, se descubre que los números
cargados eran de signos diferentes, en ese momento el valor de ok se cambia a False y se
avisa con eso que se ha descubierto una anomalía. Cuando el ciclo termina (al cargarse el 0),
el valor final de ok nos dice si los números cargados eran del mismo signo (ok = True) o no
(ok = False). El esquema (casi en pseudocódigo…) podría quedar modificado así:
# si ok es True, todos son del mismo signo hasta aquí...
ok = True
while actual != 0:
# cargar el siguiente...
actual = int(input('Cargue otro (con 0 corta): '))
El acumulador suma sólo se muestra si al finalizar el ciclo se comprueba que todos los
números eran del mismo signo. De otro modo, su valor simplemente se ignora.
Note que una vez que ok cambia a False, no vuelve ya a cambiar a True: si cambió a False al
menos una vez, significa que al menos una vez se detectaron números con signos cambiados
y esa anomalía es definitiva: no importa como siga la carga de la sucesión, el valor de ok
seguirá en False.
Para determinar si en algún momento hay un cambio de signo, la idea sería poder contar en
cada vuelta con dos números en lugar de sólo uno. Por caso, sabemos que la variable actual
contiene el número que se acaba de cargar en esa iteración, pero podríamos usar una
segunda variable llamada anterior para guardar en ella el valor del número que se cargó en
la vuelta anterior. Eso es fácil de hacer: al cargar el primer número, se asigna ese mismo en
anterior (en la primera vuelta y sólo en ella las variables actual y anterior contendrán el
mismo número) y a partir de allí se almacena en anterior el valor de actual inmediatamente
antes de cargar el siguiente valor en actual:
# si ok es True, todos son del mismo signo hasta aquí...
ok = True
while actual != 0:
anterior = actual
# cargar el siguiente...
actual = int(input('Cargue otro (con 0 corta): '))
Y como ahora tenemos el valor actual y el de la vuelta anterior, sólo debemos controlar si
ambos son del mismo signo o no. Si no fuesen del mismo signo, tendríamos detectada una
anomalía y ok cambiaría a False. La forma más sencilla se determinar si dos números son del
mismo signo o no (sabiendo que ninguno de ellos es cero) es multiplicarlos entre sí: si el
resultado es positivo, los números eran del mismo signo (los dos positivos o los dos
negativos); pero si el resultado es negativo, entonces ambos eran de signos opuestos. El
esquema puede entonces replantearse así, en forma de programa definitivo:
__author__ = 'Catedra de AED'
# cargar el siguiente...
actual = int(input('Cargue otro (con 0 corta): '))
else:
print('Los números no eran todos del mismo signo')
clase) que permite representar números complejos de la forma a + bj tal como se indicó
más arriba [3]. El factor j representa la unidad imaginaria y es automáticamente gestionado
por Python. Por lo tanto, cualquier expresión que pudiese dar como resultado un número
complejo, en Python efectivamente funcionará y entregará el complejo como resultado, sin
que el programador deba preocuparse por detalles de validación. En la siguiente secuencia
de instrucciones, se está intentando calcular la raíz cuadrada del valor -4 para asignar el
resultado en x (recuerde que calcular la raíz cuadrada es lo mismo que elevar a la potencia
0.5):
x = (-4)**0.5
print('x:', x)
Como se ve, Python realiza el cálculo obteniendo un valor de tipo complex, y lo mismo
ocurrirá cada vez que se quiera calcular cualquier raíz de valor par (raíz cuarta, raíz sexta,
etc.) de un número negativo. Note que el número en coma flotante que representa la parte
real del complejo, está expresado en este caso en notación científica: el valor flotante
1.2246467991473532e-16
equivale a:
1.2246467991473532 * 10-16
La función complex() en este caso toma dos parámetros: el primero (3.4 en el ejemplo) será
la parte real del complejo a crear, y el segundo (4 en este caso) será la parte imaginaria. el
factor j será automáticamente incluido por Python. La salida de este script será la siguiente:
c1: (3.4+4j)
3
La forma en que se representan internamente los números en coma flotante en un computador lleva a
mínimos errores de precisión que se hacen más evidentes mientras más dígitos a la derecha de la coma se
miren, pero en muchas situaciones prácticas estos errores de precisión pueden despreciarse (como en el caso
del ejemplo analizado).
Por otra parte, la misma función complex() puede ser usada de forma que se le envíe una
cadena de caracteres representando al complejo, y no ya los dos números:
c2 = complex('2-5j')
print('c2:', c2)
El script anterior producirá la siguiente salida en consola estándar:
c2: (2-5j)
Sólo debemos hacer notar que si se crea un complejo usando una cadena de caracteres
como parámetro de la función complex(), no deben introducirse espacios en blanco a los
lados del signo + o del signo – que separa ambas partes del complejo. El mismo ejemplo pero
expresado con espacios:
c2 = complex('2 - 5j')
print('c2:', c2)
Todo lo que hemos introducido hasta aquí respecto del tratamiento de números complejos
en Python, puede ser aplicado ahora en el análisis y solución de un problema clásico del
Álgebra:
Problema 17.) Un polinomio de segundo grado tiene la forma p(x) = ax2 + bx + c donde a, b, y c son
valores constantes conocidos como los coeficientes del polinomio y x es la variable independiente. Se
supone que el coeficiente a es diferente de cero (pues de lo contrario el término ax2 desaparece y el
polinomio se convierte en un de primer grado).
Si el polinomio se iguala a cero, se obtiene una ecuación de segundo grado: ax2 + bx + c = 0 y
resolver la ecuación es el proceso de hallar los valores de x que hacen que efectivamente el polinomio
valga cero.
Por el Análisis Matemático se sabe que todo polinomio de grado n tiene exactamente n raíces (reales
y/o complejas) para su ecuación y por lo tanto, una ecuación de segundo grado tiene dos raíces a las
que tradicionalmente se designa como x1 y x2. El problema de encontrar estos dos valores fue
estudiado desde varios siglos antes de Cristo al menos por los babilonios, los indios y los árabes (de
hecho, nuestro ya conocido Al-Jwarizmi contribuyó de forma significativa) y la fórmula de cálculo es
bien conocida:
−𝑏 ± √𝑏2 − 4𝑎𝑐
𝑥1−2 =
2𝑎
Discusión y solución: Está claro que los datos a ingresar son los valores de los coeficientes a,
b y c y que estos valores serán números reales (de coma flotante). Los resultados a obtener
también son claros: los valores de las raíces x1 y x2 que podrán ser números reales o
eventualmente números complejos.
El hecho que puede llevar a que las raíces sean números complejos surge de la presencia del
cálculo de la raíz cuadrada que se ve en el numerador de la fórmula. La expresión b2 – 4ac
(que se conoce como el discriminante de la ecuación) podría dar como resultado un valor
negativo, y en ese caso la raíz cuadrada no puede calcularse en el campo real. En
consecuencia, si el discriminante fuese negativo las raíces de la ecuación estarían en el
campo complejo e incluso en ese caso nuestro programa debe mostrar sus valores.
Si programador está trabajando con Python, entonces la solución a este problema es directa
y lineal, ya que Python calculará esa raíz cuadrada y obtendrá resultados reales o complejos
según sea el caso. Hay poco que discutir aquí, y mostramos la solución como un script
directo:
__author__ = 'Cátedra de AED'
seguir = 's'
while seguir == 's' or seguir == 'S':
# visualización de resultaddos...
print('x1:', x1)
print('x2:', x2)
El programa incluye un ciclo while que permite resolver una ecuación y ofrecer al usuario la
posibilidad de resolver otra. La variable seguir comienza con el valor 's' (abreviatura de 'sí')
para forzar al ciclo a entrar en la primera vuelta y resolver la primera ecuación. Pero luego
de resolverla, al final del bloque de acciones del ciclo se muestra un mensaje al usuario
preguntándole si desea resolver otra o no. La respuesta del usuario se carga por teclado en
la variable seguir, asumiendo que cargará una 's' si quería responder que 'sí', o una 'n' para
responder que 'no'. Al retornar a la cabecera del ciclo, se chequea el valor de la variable
seguir: si la misma llegó valiendo una 's' o una 'S' (habilitando que tanto la minúscula como
la mayúscula sean válidas) el ciclo ejecutará otra vuelta, cargando nuevamente valores en la
variables a, b y c y resolviendo la nueva ecuación. El ciclo se detendrá cuando el usuario no
cargue una 's' ni una 'S': en los hechos, terminará con cualquier letra diferente de esas dos.
En cuanto al resto del programa, es interesante remarcar que las variables x1 y x2 serán
asignadas con los valores correctos, sean estos de tipo float o de tipo complex, y esto es así
debido al hecho (que ya hemos estudiado) de ser Python un lenguaje de tipado dinámico [2].
Ahora bien: ¿qué pasaría en el caso que el lenguaje de programación usado no tuviese
disponible la clase complex, lista para usar e integrada en la forma de trabajo de sus
operadores aritméticos? Incluso si el programador sabe que puede usar Python, podría
presentarse este caso como un desafío práctico o como un objetivo didáctico. En resumen:
¿cómo plantear (incluso en Python) el cálculo de las raíces de la ecuación de segundo grado,
sin usar la clase complex de Python?
Ahora el trabajo es un poco más desafiante, ya que el programador deberá evitar el cálculo
de la raíz cuadrada si el discriminante es negativo, y en ese caso hacer los cálculos de los
valores de la parte real y la parte imaginaria para finalmente mostrar todo en la consola de
salida. Por lo pronto, entonces, sería prudente calcular el valor del discriminante y dejarlo
alojado en una variable:
d = b**2 - 4*a*c
La secuencia anterior hace los cálculos de ambas raíces en forma directa, aplicando las
fórmulas, pero usando el valor del discriminante que fue almacenado antes en la variable d.
Con esto se logra algo de ahorro de código fuente y también un poco de ahorro de tiempo al
ejecutar el programa, ya que no tendrá que volver a calcular dos veces más el discriminante.
Para el cálculo de las raíces complejas tendremos algo de trabajo extra. La raíces serán
complejas sólo si el discriminante d es negativo, y en este caso ambas raíces tendrán la
misma parte real y la misma parte imaginaria, pero aplicando la suma en x1 y la resta en x2
(se dice esos dos números complejos son complejos conjugados). Para deducir cuánto vale
entonces la parte real pr de ambos complejos, y cuánto vale la parte imaginaria pi de ambos,
nos basamos en el siguiente desarrollo, que se aplica en general y sin importar por ahora si
el discriminante es negativo o no (ver Figura 7):
Figura 7: Desarrollo del cálculo de las raíces de la ecuación de segundo grado
−𝑏 ± √𝑏 2 − 4𝑎𝑐
𝑥1−2 =
2𝑎
𝑏 √𝑏 2 − 4𝑎𝑐
𝑥1−2 = − ±
2𝑎 2𝑎
𝑏 √d
𝑥1−2 = − ±
2𝑎 2𝑎
Observe: si el valor inicial era d = 4, entonces –d es –4. Pero si el valor era d = –4, entonces el
valor –d es: –d = – (–4) o sea –d = 4. El valor cambiado de signo (–d) se asigna en la propia
variable d y eso provoca el cambio del signo del discriminante a los efectos del cálculo. Y
entonces, según la última parte del desarrollo que hicimos en la Figura 7, nos queda que la
parte real pr de ambos complejos x1, x2 es:
𝑏
𝑝𝑟 = −
2𝑎
Y la parte imaginaria pi de ambos complejos x1, x2, es:
√−𝑑
𝑝𝑖 = ∗𝑗
2𝑎
De este modo, el cálculo de las raíces complejas puede verse como sigue, en la rama falsa de
la condición:
if d >= 0:
# raices reales...
x1 = (-b + d**0.5) / (2*a)
x2 = (-b - d**0.5) / (2*a)
else:
# raices complejas...
# calcular la parte real...
pr = -b / (2*a)
El resultado final en este caso está formado por cadenas de caracteres: en x1 se asigna una
cadena formada por la conversión a string de los números pr y pi, unida con un par de
paréntesis, el signo más ('+') y la letra 'j' que representa a la unidad imaginaria. Algo similar
se hace con x2, pero cambiando el signo más ('+') por el menos ('-').
Note que para armar cada cadena final, las distintas partes se unen o concatenan usando el
operador suma (que justamente actúa como un concatenador cuando suma cadenas de
caracteres) [3]:
x1 = '(' + str(pr) + '+' + str(pi) + 'j)'
x2 = '(' + str(pr) + '-' + str(pi) + 'j)'
Sin embargo, recuerde que pr y pi son variables que al momento de ejecutar estas dos
instrucciones contienen números (y no cadenas). Si el intérprete encuentra que el operador
suma debe "sumar" una cadena (como ')') con un número (como pr), el programa se
interrumpirá con un mensaje de error: no pueden unirse o sumarse una cadena y un
número. Para evitar este problema, se usa la función str() de la librería estándar de Python
(ver Ficha 3, sección 3) que permite convertir un objeto cualquiera en una cadena de
caracteres. Por ejemplo, si pr vale 2.45, entonces str(pr) retornará la cadena '2.45' que es el
número original, pero en formato de string.
El programa completo para el cálculo de las raíces puede verse entonces así:
__author__ = 'Cátedra de AED'
seguir = 's'
while seguir == 's' or seguir == 'S':
else:
# raices complejas...
# calcular la parte real...
pr = -b / (2*a)
# visualización de resultaddos...
print('x1:', x1)
print('x2:', x2)
Este script une todas las piezas de la división en subproblemas que hemos propuesto (raíces
reales calculadas como un proceso separado de las raíces complejas). Luego de cargar los
valores de las variables a, b, c calcula el discriminante y deja ese valor en la variable d. Se usa
una condición para verificar el signo de d, y se calculan las raíces reales o las raíces complejas
según sea el caso. Al final, se muestran los valores finales de x1 y x2, que serán dos números
reales si las raíces eran reales, o dos cadenas de caracteres representando a los dos
complejos si las raíces eran complejas. Igual que antes, el programa incluye un ciclo while
que permite al usuario del programa cargar una nueva ecuación si así lo desea.
Créditos
El contenido general de esta Ficha de Estudio fue desarrollado por el Ing. Valerio Frittelli para ser
utilizada como material de consulta general en el cursado de la asignatura Algoritmos y Estructuras
de Datos – Carrera de Ingeniería en Sistemas de Información – UTN Córdoba, en el ciclo lectivo 2018.
Actuaron como revisores (indicando posibles errores, sugerencias de agregados de contenidos y
ejercicios, sugerencias de cambios de enfoque en alguna explicación, etc.) en general todos los
profesores de la citada asignatura como miembros de la Cátedra, y particularmente los ingenieros
Silvio Serra, Analía Guzmán, Cynthia Corso, Karina Ligorria, Marcela Tartabini y Romina Teicher, que
realizaron aportes de contenidos, propuestas de ejercicios y sus soluciones, sugerencias de estilo de
programación, y planteo de enunciados de problemas y actividades prácticas, entre otros elementos.
Bibliografía
[1] V. Frittelli, Algoritmos y Estructuras de Datos, Córdoba: Universitas, 2001.
[2] M. Pilgrim, "Dive Into Python - Python from novice to pro," 2004. [Online]. Available:
http://www.diveintopython.net/toc/index.html.