Chapter 7
Cadenas
7.1 Un tipo de datos compuesto
Hasta el momento hemos visto tres tipos: int, float, y string. Las cadenas son cuantitativamente diferentes de los otros dos
porque están hechas de piezas menores: caracteres.
Los tipos que comprenden piezas menores se llaman tipos de datos
compuestos. Dependiendo de qué estemos haciendo, podemos querer tratar
un tipo compuesto como una única cosa o acceder a sus partes. Esta
ambigüedad es útil.
El operador corchete selecciona un carácter suelto de una cadena.
>>> fruta = "banana"
>>> letra = fruta[1]
>>> print letra
La expresión fruta[1] selecciona el carácter número 1 de fruta. La variable letra apunta al resultado. Cuando mostramos
letra, nos encontramos con una sorpresa:
a
La primera letra de "banana" no es a. A no ser que usted sea un
programador. Por perversas razones, los científicos de la computación siempre
empiezan a contar desde cero. La 0-sima letra ("cerósima") de "banana"es b. La 1-ésima ("unésima") es a, y la 2-ésima ("dosésima") letra
es n.
Si quiera la cerósima letra de una cadena, simplemente pone 0, o cualquier
expresión de valor 0, entre los corchetes:
>>> letra = fruta[0]
>>> print letra
b
A la expresión entre corchetes se le llama índice. Un índice identifica
a un miembro de un conjunto ordenado, en este caso el conjunto de caracteres
de la cadena. El índice indica cuál quiere usted, de ahí el nombre. Puede
ser cualquier expresión entera.
7.2 Longitud
La función len devuelve el número de caracteres de una cadena:
>>> fruta = "banana"
>>> len(fruta)
6
Para obtener la última letra de una cadena puede sentirse tentado a probar
algo como esto:
longitud = len(fruta)
ultima = fruta[longitud]
Eso no funcionará. Provoca un error en tiempo de ejecución IndexError:
string index out of range. La razón es que no hay una sexta letra en
"banana". Como empezamos a contar por cero, las seis letras están
numeradas del 0 al 5. Para obtener el último carácter tenemos que restar
1 de longitud:
longitud = len(fruta)
ultima = fruta[longitud-1]
De forma alternativa, podemos usar índices negativos, que cuentan hacia
atrás desde el final de la cadena. La expresión fruta[-1] nos da
la última letra. fruta[-2] nos da la penúltima, y así.
7.3 Recorrido y el bucle \tt{for
\label{for}
\index{recorrido}
\index{bucle!recorrido}
\index{bucle for}
\index{bucle!for}
Muchos cálculos incluyen el proceso de una cadena carácter a carácter.
A menudo empiezan por el principio, seleccionan cada carácter por turno,
hacen algo con él y siguen hasta el final. Este patrón de proceso se llama
{\bf recorrido}. Una forma de codificar un recorrido es una sentencia
\verb|while|:
\adjustpage{2}
\beforeverb
\begin{verbatim}
indice = 0
while indice < len(fruta):
letra = fruta[indice]
print letra
indice = indice + 1
\end{verbatim}
\afterverb
%
Este bucle recorre la cadena y muestra cada letra en una línea distinta.
La condición del bucle es \verb|indice < len(fruta)|, de modo que cuando
\verb|indice| es igual a la longitud de la cadena, la condición es falsa
y no se ejecuta el cuerpo del bucle. El último carácter al que se accede
es el que tiene el índice \verb|len(fruta)-1|, que es el último carácter
de la cadena.
\begin{quote}
{\em Como ejercicio, escriba una función que tome una cadena como argumento
y entregue las letras en orden inverso, una por línea.}
\end{quote}
Es tan habitual usar un índice para recorrer un conjunto de valores que
Python facilita una sintaxis alternativa más simple: el bucle \verb|for|:
\beforeverb
\begin{verbatim}
for car in fruta:
print car
\end{verbatim}
\afterverb
%
Cada vez que recorremos el bucle, se asigna a la variable \verb|car| el
siguiente carácter de la cadena. El bucle continúa hasta que no quedan
caracteres.
\index{concatenación}
\index{abeced'arico}
\index{McCloskey, Robert}
\index{{\em Make Way for Ducklings}}
El ejemplo siguiente muestra cómo usar la concatenación junto a un
bucle {\tt for} para generar una serie abecedárica. ``Abecedárica'' es
la serie o lista en la que cada uno de los elementos aparece en orden
alfabético. Por ejemplo, en el libro de Robert McCloskey {\em Make Way
for Ducklings (Dejad paso a los patitos)}, los nombres de los patitos
son Jack, Kack, Lack, Mack, Nack, Ouack, Pack, y Quack. Este bucle
saca esos nombres en orden:
\beforeverb
\begin{verbatim}
prefijos = "JKLMNOPQ"
sufijo = "ack"
for letra in prefijos:
print letra + sufijo
\end{verbatim}
\afterverb
%
La salida del programa es:
\beforeverb
\begin{verbatim}
Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack
\end{verbatim}
\afterverb
%
Por supuesto, esto no es del todo correcto, porque ``Ouack'' y ``Quack'' no
están correctamente escritos.
\begin{quote}
{\em Como ejercicio, modifique el programa para corregir este error.}
\end{quote}
\section{Porciones de cadenas}
\label{slice}
\index{porción}
\index{cadena!porción}
Llamamos {\bf porción} a un segmento de una cadena. La selección de una porción
es similar a la selección de un carácter:
\beforeverb
\begin{verbatim}
>>> s = "Pedro, Pablo, y María"
>>> print s[0:5]
Pedro
>>> print s[7:12]
Pablo
>>> print s[15:20]
María
\end{verbatim}
\afterverb
%
El operador \verb|[n:m]| devuelve la parte de la cadena desde el enésimo
carácter hasta el ``emésimo'', incluyendo el primero pero excluyendo el último.
Este comportamiento contradice a nuestra intuición; tiene más sentido si imagina los
índices señalando {\em entre} los caracteres, como en el siguiente diagrama:
\beforefig
\centerline{\psfig{figure=../illustrations/banana.eps}}
\afterfig
Si omite el primer índice (antes de los dos puntos), la porción comienza al
principio de la cadena. Si omite el segundo índice, la porción llega al final
de la cadena. Así:
\beforeverb
\begin{verbatim}
>>> fruta = "banana"
>>> fruta[:3]
'ban'
>>> fruta[3:]
'ana'
\end{verbatim}
\afterverb
%
¿Qué cree usted que significa \verb|s[:]|?
\section{Comparación de cadenas}
\index{comparación de cadenas}
\index{comparación!cadenas}
Los operadores de comparación trabajan sobre cadenas. Para ver si dos cadenas
son iguales:
\beforeverb
\begin{verbatim}
if palabra == "banana":
print "Sí, no tenemos bananas!"
\end{verbatim}
\afterverb
%
\adjustpage{-2}
Otras operaciones de comparación son útiles para poner palabras en orden
alfabético:
\beforeverb
\begin{verbatim}
if palabra < "banana":
print "Tu palabra," + palabra + ", va antes de banana."
elif palabra > "banana":
print "Tu palabra," + palabra + ", va después de banana."
else:
print "Sí, no tenemos bananas!"
\end{verbatim}
\afterverb
%
Sin embargo, debería usted ser consciente de que Python no maneja las
mayúsculas y minúsculas como lo hace la gente. Todas las mayúsuculas
van antes de la minúsculas. Como resultado de ello:
\beforeverb
\begin{verbatim}
Tu palabra, Zapato, va antes de banana.
\end{verbatim}
\afterverb
%
Una forma común de abordar este problema es convertir las cadenas a un
formato estándar, como pueden ser las minúsculas, antes de realizar la
comparación. Un problema mayor es hacer que el programa se dé cuenta de
que los zapatos no son frutas.
\section{Las cadenas son inmutables}
\index{mutable}
\index{cadena inmutable}
\index{cadena!inmutable}
Es tentador usar el operador \verb|[]| en el lado izquierdo de una
asignación, con la intención de cambiar un carácter en una cadena.
Por ejemplo:
\beforeverb
\begin{verbatim}
saludo = "Hola, mundo"
saludo[0] = 'M' # ERROR!
print saludo
\end{verbatim}
\afterverb
%
En lugar de presentar la salida \verb|Mola, mundo|, este código presenta
el siguiente error en tiempo de ejecución {\tt TypeError: object doesn't
support item assignment}.
\index{error en tiempo de ejecución}
Las cadenas son {\bf inmutables}, lo que significa que no puede
cambiar una cadena existente. Lo más que puede hacer es crear una
nueva cadena que sea una variación de la original:
\beforeverb
\begin{verbatim}
saludo = "Hola, mundo"
nuevoSaludo = 'M' + saludo[1:]
print nuevoSaludo
\end{verbatim}
\afterverb
%
Aquí la solución es concatenar una nueva primera letra a una porción de
\verb|saludo|. Esta operación no tiene efectos sobre la cadena original.
\index{concatenación}
\adjustpage{-2}
\section{Una función ``encuentra''}
\label{encuentra}
\index{recorrido}
\index{recorrido eureka}
\index{patrón}
\index{patrón computacional}
¿Qué hace la siguiente función?
\beforeverb
\begin{verbatim}
def encuentra(cad, c):
indice = 0
while indice < len(cad):
if cad[indice] == c:
return indice
indice = indice + 1
return -1
\end{verbatim}
\afterverb
%
En cierto sentido, \verb|encuentra| es lo contrario del operador
\verb|[]|. En lugar de tomar un índice y extraer el carácter
correspondiente, toma un carácter y encuentra el índice donde
aparece el carácter. Si el carácter no se encuentra, la función
devuelve {\tt -1}.
Este es el primer ejemplo que hemos visto de una sentencia \verb|return|
dentro de un bucle. Si \verb|cad[indice] == c|, la función vuelve
inmediatamente, escapando del bucle prematuramente.
Si el carácter no aparece en la cadena, el programa sale del bucle
normalmente y devuelve \verb|-1|.
Este patrón de computación se llama a veces un recorrido ``eureka''
porque en cuanto encontramos lo que buscamos, podemos gritar ``¡Eureka!''
y dejar de buscar.
\begin{quote}
{\em A modo de ejercicio, modifique la función \verb|encuentra| para que
acepte un tercer parámetro, el índice de la cadena donde debería empezar
a buscar.}
\end{quote}
\section{Bucles y conteo}
\label{contador}
\index{contador}
\index{patrón}
El programa que sigue cuenta el número de veces que la letra \verb|a|
aparece en una cadena:
\beforeverb
\begin{verbatim}
fruta = "banana"
cuenta = 0
for car in fruta:
if car == 'a':
cuenta = cuenta + 1
print cuenta
\end{verbatim}
\afterverb
%
Este programa muestra otro patrón de computación llamado {\bf contador}.
La variable \verb|cuenta| se incializa a 0 y luego se incrementa cada
vez que se encuentra una \verb|a|. ({\bf Incrementar} es aumentar en
uno; es lo contario de {\bf decrementar}, y sin relación alguna con
``excremento'', que es un nombre.) Al salir del bucle, \verb|cuenta|
contiene el resultado -- el número total de \verb|a|es.
\begin{quote}
{\em Como ejercicio, encapsule este código en una función llamada {\tt
cuentaLetras}, y generalícela de forma que acepte la cadena y la letra
como parámetros.}
\end{quote}
\begin{quote}
{\em Como un segundo ejercicio, reescriba esta función para que en lugar
de recorrer la cadena, use la versión de tres parámetros de {\tt encuentra
del anterior}.}
\end{quote}
\section{El módulo ``string''}
\index{módulo}
\index{módulo string}
El módulo \verb|string| contiene funciones útiles para manipular cadenas.
Como es habitual, tenemos que importar el módulo antes de poder usarlo:
\beforeverb
\begin{verbatim}
>>> import string
\end{verbatim}
\afterverb
%
El módulo \verb|string| incluye una función llamada \verb|find| que hace lo
mismo que la función \verb|encuentra| que escribimos. Para llamarla debemos
especificar el nombre del módulo y el nombre de la función por medio de la
notación de punto.
\beforeverb
\begin{verbatim}
>>> fruta = "banana"
>>> indice = string.find(fruta, "a")
>>> print indice
1
\end{verbatim}
\afterverb
%
Este ejemplo demuestra uno de los beneficios de los módulos: ayudan a evitar
las colisiones entre los nombres de las funciones predefinidas y las definidas
por el usuario. Al usar la notación de punto podríamos especificar qué versión
de \verb|find| queremos en caso de haberle daddo un nombre en inglés a nuestra
función.
En realidad, \verb|string.find| es más general que nuestra versión. Para
empezar, puede encontrar subcadenas, no sólo caracteres:
\beforeverb
\begin{verbatim}
>>> string.find("banana", "na")
2
\end{verbatim}
\afterverb
%
Además, acepta un argumento adicional que especifica el índice en el que
debería comenzar:
\beforeverb
\begin{verbatim}
>>> string.find("banana", "na", 3)
4
\end{verbatim}
\afterverb
%
O puede tomar dos argumentos adicionales que especifican un intervalo de
índices:
\beforeverb
\begin{verbatim}
>>> string.find("sus", "s", 1, 2)
-1
\end{verbatim}
\afterverb
%
En este ejemplo, la búsqueda falla porque la letra {\em s} no aparece en
el intervalo de índices desde \verb|1| hasta \verb|2| (sin incluir {\tt 2}).
\section{Clasificación de caracteres}
\label{in}
\index{clasificación de caracteres}
\index{clasificación!carácter}
\index{uppercase}
\index{lowercase}
\index{whitespace}
A menudo viene bien examinar un carácter y comprobar si es una letra
mayúscula o minúscula, o si es un carácter o un dígito. El módulo
\verb|string| proporciona varias constantes que son útiles para estos
menesteres.
La cadena \verb|string.lowercase| contiene todas las letras que el sistema
considera como minúsculas. De forma similar, \verb|string.uppercase|
contiene todas las mayúsculas. Pruebe lo que sigue y vea qué obtiene:
\beforeverb
\begin{verbatim}
>>> print string.lowercase
>>> print string.uppercase
>>> print string.digits
\end{verbatim}
\afterverb
%
Podemos usar estas constantes y \verb|find| para clasificar caracteres. Por
ejemplo, si \verb|find(lowercase, c)| devuelve un valor que no sea {\tt -1},
entonces \verb|c| es una minúscula:
\beforeverb
\begin{verbatim}
def esMinuscula(c):
return find(string.lowercase, c) != -1
\end{verbatim}
\afterverb
%
Alternativamente, podemos aprovecharnos del operador \verb|in|, que
determina si un carácter aparece en una cadena:
\beforeverb
\begin{verbatim}
def esMinuscula(c):
return c in string.lowercase
\end{verbatim}
\afterverb
%
Como una alternativa más, podemos usar el operador de comparación,
aunque esta solución sólo sea práctica para el alfabeto inglés:
\beforeverb
\begin{verbatim}
def esMinuscula(c):
return 'a' <= c <= 'z'
\end{verbatim}
\afterverb
%
Si \verb|c| está entre {\em a} y {\em z}, tiene que ser una minúscula.
\begin{quote}
{\em Como ejercicio, explique qué versión de \verb|esMinuscula| cree que
es más rápida. ¿Puede pensar en otras razones aparte de la velocidad para
preferir una sobre la otra?}
\end{quote}
Otra constante definida en el módulo \verb|string| puede sorprenderle cuando
la imprima:
\beforeverb
\begin{verbatim}
>>> print string.whitespace
\end{verbatim}
\afterverb
%
Los caracteres de {\bf whitespace} mueven el cursor sin imprimir nada. Crean
los espacios en blanco entre los caracteres visibles (al menos sobre papel
blanco). La constante \verb|string.whitespace| contiene todos los caracteres
de espacio en blanco, incluídos espacio, tabulador (\verb+\t+), y salto de
línea (\verb+\n+).
\index{módulo string}
\index{módulo!string}
Hay otras funciones útiles en el módulo \verb|string|, pero este libro
no pretende ser un manual de referencia. Por otra parte, la {\em Referencia
de la Biblioteca de Python} sí lo es. Junto con un montón más de documentación,
está disponible en el sitio web de Python, {\tt www.python.org}.
\index{{\em Referencia de la Biblioteca de Python}}
\section{Glosario}
\begin{description}
\item[tipo de datos compuesto:] Un tipo de datos en el que los valores
están hechos de componentes o elementos que son a su vez valores.
\item[recorrer:] Realizar de forma iterativa una operación similar sobre
cada uno de los elementos de un conjunto.
\item[índice:] Una variable o valor usado para seleccionar un miembro de
un conjunto ordenado, como puede ser un carácter de una cadena.
\item[porción:] Una parte de una cadena especificada por un intervalo de
índices.
\item[mutable:] Un tipo de datos compuesto a cuyos elementos se les puede
asignar nuevos valores.
\item[contador:] Una variable usada para contar algo, normalmente inicializado
a cero e incrementado posteriormente.
\item[incrementar:] Aumentar el valor de una variable en una unidad.
\item[decrementar:] Disminuir el valor de una variable en una unidad.
\item[espacio en blanco:] Cualquiera de los caracteres que mueven el cursor
sin imprimir caracteres visibles. La constante \verb|string.whitespace|
contiene todos los caracterse de espacio en blanco.
\index{tipo compuesto de datos}
\index{recorrer}
\index{índice}
\index{porción}
\index{mutable}
\index{contador}
\index{incrementar}
\index{decrementar}
\index{espacio en blanco}
\end{description}