Chapter 5
\documentclass[oneside,english]{book
\usepackage[T1]{fontenc}
\usepackage[latin1]{inputenc}
\usepackage{babel}
\setcounter{secnumdepth}{3}
\setcounter{tocdepth}{3}
\usepackage{graphics}
\makeatletter
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands.
\providecommand{\LyX}{L\kern-.1667em\lower.25em\hbox{Y}\kern-.125emX\@}
\makeatother
\begin{document}
\chapter{Funciones rentables}
\section{Valores de retorno}
\index{valores de retorno}
Algunas de las funciones internas que hemos usado, como las funciones
math o funciones matemáticas, han producido resultados. Llamar a la
función genera un nuevo valor, que normalmente asignamos a una variable
pasa usar como parte de una expresión. Por ejemplo:
\begin{verbatim}
import math
e = math.exp(1.0)
altura = radio * math.sin(angle)
\end{verbatim} Pero hasta ahora, ninguna de las funciones que hemos
escrito ha devuelto un valor. Cuando Usteld llama a una función que
no devuelve valor, normalmente es de una línea por sí misma, sin asignación:
\begin{verbatim}
nLineas(3)
cuentraAtras(n-1)
\end{verbatim} En este capítulo escribiremos funciones que devuelvan
valores, que llamaremos \textbf{funciones rentables}, a falta de un
nombre mejor. El primer ejemplo es \texttt{area}, que devuelve el
área de un círculo con un radio dado:
\begin{verbatim}
import math
def area(radio):
temporal = math.pi * radio**2
return temporal
\end{verbatim} En la última línea de la función, la sentencia \texttt{return}
incluye un valor de retorno. Esta sentencia quiere decir {}``retorna
inmediatamente de la función y usa la siguiente expresión como valor
de retorno''. La expresión dada puede ser arbitrariamente complicada;
así pues, podríamos haber escrito esta función más consisamente:
\begin{verbatim}
def area(radio):
return math.pi * radio**2
\end{verbatim} Por otra parte, las \textbf{variables temporales} como
\texttt{temporal} normalmente hacen más fácil el depurado.
\index{variables temporales} \index{temporales!variables}
A veces es útil disponer de varias sentencias de retorno, una en cada
rama de una condición:
\begin{verbatim}
def valorAbsoluto(x):
if x < 0:
return -x
else:
return x
\end{verbatim} Puesto que estas sentencias \texttt{return} están en
una condición alternante, sólo se ejecutará una de ellas. Aunque es
legal tener más de una sentencia \texttt{return} en una función, en
cuanto se ejecuta una de ellas la función termina sin ejecutar ninguna
de las sentencias siguientes.
El código que aparece después de una sentencia \texttt{return} o en
cualquier otro lugar donde no pueda ejecutarse nunca, recibe el nombre
de \textbf{código muerto}. No puede ejecutarse nunca porque ningún
posible flujo de ejecución del programa puede llegar a él.
\index{código muerto}
Si ponen sentencias \texttt{return} dentro de una condición, ha de
verificar que cualquier posible flujo de ejecución a través del programa
alcanza una sentencia \texttt{return}. Por ejemplo:
\begin{verbatim}
def valorAbsoluto(x):
if x < 0:
return -x
elif x > 0:
return x # ERROR!!
\end{verbatim} Este programa no es correcto porque si resulta que
\texttt{x} vale 0, entonces no se cumple ninguna de ambas condiciones
y la función termina sin alcanzar la setencia \texttt{return}. En
este caso, el valor de retorno es un valor especial llamado \textbf{None}:
\index{None}
\begin{verbatim}
>>> print valorAbsoluto(0)
None
\end{verbatim}
\begin{quote}
\emph{Como actividad, escriba una función} \texttt{\emph{comparar}}
\emph{que devuelva} \texttt{\emph{1}} \emph{si} \texttt{\emph{x >
y}}\emph{,} \texttt{\emph{0}} \emph{si} \texttt{\emph{x == y}}\emph{,
y} \texttt{\emph{-1}} \emph{si} \texttt{\emph{x < y}}\emph{.}
\end{quote}
\section{Desarrollo de programas}
\label{program development} \index{apoyo!código de}\index{código de apoyo}
Llegados a este punto, tendría que poder mirar a funciones Python
completas y adivinar qué hacen. También tienen cierta experiencia
en hacer pequeñas modificaciones a funciones existentes. Puede que
todavía no le resulte claro, sin embargo, cómo escribir una función
desde cero.
Vamos a sugerirle una técnica que llamaremos \textbf{desarrollo incremental}.
\index{desarrollo incremental} \index{métodos de desarrollo!desarrollo incremental}
Imagínese que desea encontrar la distancia entre dos puntos, dados
por las coordenadas \( (x_{1},y_{1}) \) y \( (x_{2},y_{2}) \). Por
el teorema de Pitágoras, podemos escribir la distancia entre los dos
puntos de la siguiente manera:
\begin{equation}
distancia=\sqrt{(x_{2}-x_{1})^{2}+(y_{2}-y_{1})^{2}}
\end{equation}
El primer paso es considerar qué aspecto tendría una función \texttt{distancia}
en Python. En otras palabras, ¿cuáles son las entradas (parámetros)
y cuál es la salida (valor de retorno?
En este caso, los dos puntos son los parámetros, que podemos representar
usando cuatro parámetros. El valor de retorno es la distancia.
Ya podemos escribir un bosquejo de la función:
\begin{verbatim}
def distancia(x1, y1, x2, y2):
return 0.0
\end{verbatim} La sentencia \texttt{return} es una salvaguarda para
que cuando la función se ejecute devuelva algo , incluso aunque no
sea la respuesta correcta. En este punto, la función no hace nada
útil, pero vale la pena intentar ejecutarla, de forma que podamos
identificar errores de sintaxis antes de hacerla más complicada.
Para comprobar la nueva función, tenemos que llamarla con valores
de ejemplo, como en la siguiente sesión:
\begin{verbatim}
>>> def distancia(x1, y1, x2, y2):
... return 0.0
...
>>> distancia(1, 2, 4, 6)
0.0
>>>
\end{verbatim} Elegimos estos valores de tal forma que la distancia
horizontal sea igual a 3 y la distancia vertical sea igual a 4; de
esa manera el resultado es 5 (la hipotenusa del triángulo 3-4-5).
Cuando se comprueba una función, es útil saber la respuesta correcta.
Una vez que hayamos comprobado la sintaxis de la definición de la
función, podemos empezar a añadir líneas de código, a razón de una
cada vez. Después de cada cambio incremental, comprobamos de nuevo
la función. De esa manera sabremos en cada momento dónde está exactamente
el error: en la última línea que hayamos añadido.
El siguiente paso en el cálculo es encontrar las diferencias entre
\( x_{2}-x_{1} \) y \( y_{2}-y_{1} \). Almacenaremos dichos valores
en variables temporales llamadas \texttt{dx} y \texttt{dy}.
\begin{verbatim}
def distancia(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
print "dx is", dx
print "dy is", dy
return 0.0
\end{verbatim} Hemos añadido sentencias \texttt{print} que nos permiten
comprobar los valores intermedios antes de proseguir. De antemano
sabemos que han de ser 3 y 4.
Cuando la función esté terminada, eliminaremos las sentencias \texttt{print}.
Este código se llama \textbf{código de apoyo} porque es útil para
construir el programa pero no es parte del producto final. A veces
es una buena idea mantener el código de apoyo dentro de la función,
pero comentado, por si acaso lo necesitamos más tarde.
El siguiente paso en el desarrollo es elevar al cuadrado \texttt{dx}
y \texttt{dy}:
\begin{verbatim}
def distancia(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
print "dsquared es: ", dsquared
return 0.0
\end{verbatim} De nuevo querremos ejecutar el programa en este estado
y comprobar la variable intermedia (que debiera dar 25).
Por último, si hemos importado el módulo math, podemos usar la función
\texttt{sqrt} para calcular y devolver el resultado:
\begin{verbatim}
def distancia(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
result = math.sqrt(dsquared)
return result
\end{verbatim} Conforme vaya ganando experiencia en la programación,
puede que se encuentre escribiendo y depurando más de una línea cada
vez. Sin embargo, el proceso de desarrollo incremental puede ahorrarle
mucho tiempo de depurado.
Los aspectos fundamentales del proceso son:
\begin{enumerate}
\item Comience con un programa que funcione y hágale pequeños cambios incrementales.
Si hay un error, sabrá exactamente dónde está.
\item Use variables temporales para mantener valores intermedios, de tal
manera que pueda mostrarlos por pantalla y comprobarlos.
\item Una vez que el programa esté funcionando, tal vez prefiera eliminar
parte del código de apoyo o aglutinar múltiples sentencias en expresiones
compuestas, pero sólo si eso no hace que el programa sea difícil de
leer.
\end{enumerate}
\begin{quote}
\emph{Como actividad, utilice el desarrollo incremental para escribir
una función de nombre} \texttt{\emph{hypotenusa}} \emph{que devuelva
la longitud de la hipotenusa de un triángulo rectángulo, dando como
parámetros los dos catetos. Registre cada estado del desarrollo incremental
según vaya avanzando.}
\end{quote}
\section{Composición}
\index{composición} \index{funciones!composición}
Como seguramente a estas alturas ya supondrá, en cuanto defina una
función nueva podrá usarla como parte de de una expresión, y puede
construir funciones nuevas usando funciones existentes. Por ejemplo,
¿qué pasa si alguien le da dos puntos, el centro del círculo y un
punto del perímetro, y le pide el área del círculo?
Supongamos que el punto central está almacenado en las variables \texttt{xc}
e \texttt{yc}, y que el punto del perímetro lo está en \texttt{xp}
e \texttt{yp}. El primer paso es hallar el radio del círculo, que
es la distancia entre los dos puntos. Afortunadamente hay una función
, \texttt{distancia}, que realiza esta tarea:
\begin{verbatim}
radio = distancia(xc, yc, xp, yp)
\end{verbatim} El segundo paso es encontrar el área de un círculo
con ese radio y devolverla:
\begin{verbatim}
result = area(radio)
return result
\end{verbatim} Envolviendo todo esto en una función, obtenemos:
\begin{verbatim}
def area2(xc, yc, xp, yp):
radio = distancia(xc, yc, xp, yp)
result = area(radio)
return result
\end{verbatim} Hemos llamado a esta función \texttt{area2} para distinguirla
de la función \texttt{area} definida anteriormente. Sólo puede haber
una única función con un determinado nombre dentro de un módulo. Hablaremos
más en profundidad sobre esta cuestión cuando más adelante tratemos
los módulos y los espacios de nombres.
Las variables temporales \texttt{radio} y \texttt{area} son útiles
para el desarrollo y el depurado, pero una vez que el programa está
funcionando, podemos hacerlo más conciso integrando las llamadas a
las funciones en una sola línea:
\begin{verbatim}
def area2(xc, yc, xp, yp):
return area(distancia(xc, yc, xp, yp))
\end{verbatim}
\begin{quote}
\emph{Como actividad, escriba una función} \texttt{\emph{pendiente(x1,
y1, x2, y2)}} \emph{que devuelva la pendiente de la línea que atraviesa
los puntos (x1, y1) y (x2, y2). Luego use esta función en una función
que se llame} \texttt{\emph{intercepta(x1, y1, x2, y2)}} \emph{que
devuelva la {[}{[}y-intercept{]}{]} de la línea a través de los puntos
(x1, y1) y (x2, y2).}
\end{quote}
\section{Funciones booleanas}
\label{boolean} \index{funciones booleanas} \index{booleanas!funciones}
Las funciones pueden devolver valores booleanos, lo que a menudo es
conveniente para ocultar complicadas comprobaciones dentro de funciones.
Por ejemplo:
\begin{verbatim}
def esDivisible(x, y):
if x % y == 0:
return 1 # it's true
else:
return 0 # it's false
\end{verbatim} La función lleva por nombre \texttt{esDivisible}. Es
común dar a las funciones booleanas nombres que suenan como preguntas
sí/no. Se devuelve \texttt{1} ó \texttt{0} para indica si el argumento
que se le pasa es o no es un un dígito sencillo.
Podemos reducir el tamaño de la función aprovechándonos del hecho
de que la sentencia condicional que hay después del \texttt{if} es
en sí misma una expresión booleana. Podemos devolverla directamente,
evitando a la vez la sentencia \texttt{if:}
\begin{verbatim}
def esDivisible(x, y):
return x % y == 0
\end{verbatim} La siguiente sesión muestra a la nueva función en acción:
\begin{verbatim}
>>> esDivisible(6, 4)
0
>>> esDivisible(6, 3)
1
\end{verbatim} El uso más común para las funciones booleanas es dentro
de sentencias condicionales:
\begin{verbatim}
if esDivisible(x, y):
print "x es divisible entre y"
else:
print "x no es divisible entre y"
\end{verbatim}
\begin{quote}
\emph{Como actividad, escriba una función} \texttt{\emph{estaEntre(x,
y, z)}} \emph{que devuelva} \texttt{\emph{1}} \emph{en caso de que}
\texttt{\emph{y <= x <= z}} \emph{y que devuelva 0 en cualquier otro
caso.}
\end{quote}
\section{Más recursividad}
\index{recursividad}
Hasta ahora, Usted ha aprendido solamente un pequeño subconjunto de
Python, pero puede que le interese saber que ese subconjunto es ya
un \emph{completo} lenguaje de programación; con esto queremos decir
que cualquiera que pueda computarse se puede expresar en este lenguaje.
Cualquier programa que se haya escrito alguna vez puede reescribirse
utilizando únicamente las características del lenguaje que ha aprendido
hasta el momento (de hecho, necesitaría algunas órdenes para controlar
dispositivos como el teclado, el ratón, los discos, etc, pero eso
es todo).
Probar tal afirmación es un ejercicio nada trivial, completado por
primera vez por Alan Turing, uno de los primeros científicos informáticos
(algunos argumentarán que era un matemático, pero muchos de los científicos
informáticos pioneros comenzaron como matemáticos). En correspondencia,
se la conoce como la tesis de Turing. Si estudia un curso de Teoría
de la Computación, tendrá oportunidad de ver la prueba.
Para darle una idea de lo que puede hacer con las herramientas que
ha aprendido hasta ahora, evaluaremos una serie de funciones matemáticas
que se definen recursivamente. Una definición recursiva es semejante
a una definición circular, en el sentido de que la definición contiene
una referencia a lo que se define. Una definición verdaderamente circular
no es muy útil:
\begin{description}
\item [frangoso:]adjetivo que describe algo que es frangoso
\end{description}
Si Usted viera esa definición en el diccionario, se quedaría confuso.
Por otra parte, si ha buscado la definición de la función matemática
factorial, habrá visto algo sejemante a lo siguiente:
\begin{eqnarray*}
& & 0!=1\\
& & n!=n\cdot (n-1)!
\end{eqnarray*}
Esta definición establece que el factoral de 0 es 1, y que el factorial
de cualquier otro valor, \( n \), es \( n \) multiplicado por el
factorial de \( n-1 \). Asi pues, \( 3! \) es 3 veces \( 2! \),
que es 2 veces \( 1! \), que es una vez \( 0! \). Juntándolos todos,
, \( 3! \) es igual a 3 veces 2 veces 1 vez 1, que es 6.
Si puede escribir una definición recursiva de algo, normalmente podrá
escribir un programa de Python para evaluarlo. El primer paso es decidir
cuáles son los parámetros para esta función. Con poco esfuerzo llegará
a la conclusión de que \texttt{factorial} toma un único parámetro:
\begin{verbatim}
def factorial(n):
\end{verbatim} Si resultase que el argumento fuese 0, todo lo que
hemos de hacer es devolver 1:
\begin{verbatim}
def factorial(n):
if n == 0:
return 1
\end{verbatim} En otro caso, y he aquí la parte interesante, tenemos
que hacer una llamada recursiva para hallar el factorial de \( n-1 \)
y luego multiplicarlo por \( n \):
\begin{verbatim}
def factorial(n):
if n == 0:
return 1
else:
recursivo = factorial(n-1)
resultado = n * recursivo
return resultado
\end{verbatim} Nótese que el flujo de ejecución de este programa es
similar al de \texttt{nLinas} del Capíulo 4. Si llamamos a \texttt{factorial}
con el valor 3:
Puesto que 3 no es 0, tomamos la segunda rama y calculamos el factorial
de \texttt{n-1}...
\begin{quote}
Puesto que 2 no es 0, tomamos la segunda rama y calculamos el factorial
de \texttt{n-1}...
\begin{quote}
Puesto que 1 no es 0, tomamos la segunda rama y calculamos el factorial
de \texttt{n-1}...
\begin{quote}
Puesto que 0 \emph{is} 0, tomamos la primera rama y devolvemos el
valor 1 inmediatamente sin hacer más llamadas recursivas.
\end{quote}
El valor de retorno (1) se multiplica por \( n \), que es 1, y se
devuelve el resultado.
\end{quote}
El valor de retorno (1) se multiplica por \( n \), que es 2, y se
devuelve el resultado.
\end{quote}
El valor de retorno (2) se multiplica por \( n \), que es 3, y el
resultado 6, se convierte en el valor de retorno de la llamada a la
función que comenzó todo el proceso.
He aquí el aspecto que tiene el diagrama de pila para esta secuencia
de llamadas a función:
\vspace{0.1in} \centerline{\includegraphics{../../../thinkCSpy_tex/stack3.eps} } \vspace{0.1in}
Los valores de retorno se muestran según se pasan hacia la parte superior
de la pila.
Nótese que en el último ejemplo de \texttt{factorial}, las variables
locales \texttt{recurs}ivo y \texttt{resultado} no existen porque
cuando \( n=0 \), la rama que las crea no se ejecuta.
\section{Salto de fe}
Seguir el flujo de ejecución es una de las maneras de leer programas;
pero como ya vio en la sección 5.6, puede volverse rápidamente una
tarea laberínitca. La alternativa es lo que llamamos el {}``salto
de fe''. Cuando llegamos a una función, damos por sentado que la
función trabaja correctamente y devuelve el valor apropiado.
De hecho, Usted ya practica dicho salto de fe cuando usa funciones
internas. Cuando llama a \texttt{math.cos} o a \texttt{math.exp},
no examina la implementación de dichas funciones. Simplemente da por
sentado que funcionan porque los que escribieron las bibliotecas internas
de Python son buenos programadores.
Lo mismo se aplica cuando llama a una de las funciones programadas
por Usted. Por ejemplo en la sección~\ref{boolean}, escribimos una
función llamada \texttt{esDivisible} que determina si un número es
divisible por otro. Una vez que nos hayamos convencido de que dicha
función es correcta, comprobando y examinando el código, podremos
usar la función sin tener siquiera que volver a mirar el código otra
vez.
Lo mismo vale para los programas recursivos. Cuando llegue a la llamada
recursiva, en lugar de seguir el flujo de ejecución, tendría que dar
por supuesto que la llamada recursiva funciona (es decir, devuelve
el resultado correcto) y luego preguntarse: {}``suponiendo que puedo
hallar el factorial de \( n-1 \), ¿puedo hallar el factorial de \( n \)?''
En este caso, está claro que sí puede, multiplicándolo por \( n \).
Por supuesto, es un tanto extraño dar por supuesto que la función
está bien cuando ni siquiera ha acabado de escribirla, pero precisamente
por eso se llama salto de fe.
\section{Un ejemplo más}
\label{one more example}
En el ejemplo anterior, usamos variables temporales para ir apuntando
los resultados y para hacer que el código fuese más fácil de depurar,
pero podríamos habernos ahorrado unas cuantas líneas:
\begin{verbatim}
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
\end{verbatim} De ahora en adelante, tenderemos a usar la versión
más concisa, pero le recomendamos que utilice la versión más explícita
mientras se halle desarrollando código. Cuando lo tenga funcionando,
lo podrá acortar, si se siente inspirado.
Después de \texttt{factorial}, el ejemplo más común de una función
matemática recursivamente definida es \texttt{fibonacci}, que presenta
la definición siguiente:
\begin{eqnarray*}
& & fibonacci(0)=1\\
& & fibonacci(1)=1\\
& & fibonacci(n)=fibonacci(n-1)+fibonacci(n-2);
\end{eqnarray*}
Traducido a Python, es como sigue:
\begin{verbatim}
def fibonacci (n):
if n == 0 or n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
\end{verbatim} Si intenta seguir el flujo de ejecución aquí, incluso
para valores relativamente pequeños de \( n \), le puede dar un dolor
de cabeza. Pero si confiamos en el salto de fe, si da por supuesto
que las dos llamadas recursivas funcionan correctamente, entonces
estará claro que obtiene el resultado correcto al sumarlas juntas.
\section{Comprobación de tipos}
\index{tipos!comparación}
¿Qué sucede si llamamos a \texttt{factorial} y le damos 1.5 como argumento?
\begin{verbatim}
>>> factorial (1.5)
RuntimeError: Maximum recursion depth exceeded
\end{verbatim} Tiene todo el aspecto de una recursión infinita. Pero,
¿cómo ha podido ocurrir? Hay una condición de salida o caso base:
cuando \texttt{n == 0}. El problema es que falta la condición de salida
o caso base: en la primera llamada recursiva, el valor de \texttt{n}
es 0.5. En la siguiente vez su valor es -0.5. A partir de ahí, se
vuelve más y más pequeño, pero nunca será 0.
Tenemos dos opciones. Podemos intentar generalizar la función \texttt{factorial}
para que trabaje con números de coma flotante, o podemos hacer que
\texttt{factorial} compruebe el tipo de su parámetro. La primera opción
ya está hecha: se llama la función \texttt{Gamma}. Así pues, tomemos
la segnunda.
Podemos usar la función \texttt{type} para comparar el tipo del parámetro
con el tipo de un valor entero conocido (por ejemplo 1). Ya que estamos
en ello, podemos asegurarnos de que el parámetro sea positivo:
\begin{verbatim}
def factorial (n):
if type(n) != type(1):
print "El factorial esta definido solo para enteros."
return -1
elif n < 0:
print "El factorial esta definido solo para enteros positivos."
return -1
elif n == 0:
return 1
else:
return n * factorial(n-1)
\end{verbatim} Ahora, en efecto, tenemos tres condiciones de salida
o casos base. El primero filtra los números no enteros. El segundo
evita los enteros negativos. En ambos casos, se muestra un mensaje
de error y se devuelve un valor especial, -1, para indicar que algo
fue mal a quien hizo la llamada a la función:
\begin{verbatim}
>>> factorial (1.5)
El factorial esta definido solo para enteros.
-1
>>> factorial (-2)
El factorial esta definido solo para enteros positivos.
-1
>>> factorial ("fred")
El factorial esta definido solo para enteros.
-1
\end{verbatim} Si pasamos ambas comprobaciones, entonces sabemos que
\( n \) es un entero positivo y podemos probar que la recursión termina.
A este estilo de programación se le llama a veces \textbf{patrón guardián}.
Las primeras dos condicionales actúan como guardianes, protegiendo
al código que sigue de los valores que pudieran causar errores. Los
guardianes hacen posible demostrar la corrección del código.
\section{Glosario}
\begin{description}
\item [función~productiva:]Función que devuelve un valor de retorno.
\item [valor~de~retorno:]El valor obtenido como resultado de una llamada
a una función.
\item [variable~temporal:]Variable utilizada para almacenar un valor intermedio
en un cálculo complejo.
\item [código~muerto:]Parte de un programa que no podrá ejecutarse nunca,
a menudo debido a que aparece tras una sentencia de \texttt{return}.
\item [None:]Valor especial de Python que devuelven funciones que o bien
no tienen sentencia de \texttt{return} o bien tienen una sentencia
de \texttt{return} sin argumento. También es posible asignar este
valor a una variable como en la sentencia \texttt{x = None}. Es equivalente
a un booleano {}``falso'' o a una lista vacía.
\item [desarrollo~incremental:]Un método de desarrollo de programas que
utiliza pequeños cambios poco a poco en un programa que funciona,
a fin de modificarlo gradualmente hasta que haga lo que se pretendía
que hiciera.
\item [andamiaje:]El código que se usa durante el desarrollo del programa
pero que no es parte de la versión final.
\item [guardián:]Una condición que comprueba y maneja circunstancias que
pudieran provocar un error.
\index{variable!temporall} \index{temporal!variable} \index{valor de retorno}
\index{código muerto} \index{None} \index{desarrollo incremental}
\index{andamiaje} \index{guardián}\end{description}
\end{document}