4. Cadenas de caracteres

Las cadenas de caracteres (denominadas habitualmente y de manera indistinta como strings) son un tipo de dato que contiene una secuencia de símbolos, mismos que pueden ser alfanúmericos o cualquier otro símbolo propio de un sistema de escritura. En Python los strings se definen utilizando comillas dobles o simples. Observa los siguientes ejemplos:

nombre = "Catalina"
type(nombre)
str
apellido = 'Lara'
type(apellido)
str

Observa que es indistinto utilizar las comillas dobles o simples al momento de crear una cadena de caracteres. Si se requiere crear una cadena de caracteres multilínea, entonces se pueden definir utilizando un tres comillas dobles, tal como se muestra en el siguiente ejemplo:

en_paz = """
Muy cerca de mi ocaso, yo te bendigo, vida,
porque nunca me diste ni esperanza fallida,
ni trabajos injustos, ni pena inmerecida;
"""
type(en_paz)
str

La longitud o cantidad de elementos de una cadena se puede determinar utilizando la función len:

palabra = "Hola"
len(palabra)
4

4.1. Concatenación de cadenas

Para concatenar (unir) cadenas se puede utilizar el operador +. Observa el siguiente ejemplo en el cual se concatenan las cadenas "hola" y "mundo".

"Hola" + "mundo"
'Holamundo'

Notarás que Python por sí mismo no sabe que estamos uniendo dos palabras y que entre ellas debería haber un espacio para su correcta lectura, evidentemente este tipo de cuestiones son las que el programador debe tomar en cuenta al escribir un código. Si quisiéramos introducir un espacio entre las dos palabras podríamos añadirlo de forma manual en alguna de las cadenas, o como una tercera cadena intermedia, tal como se muestra enseguida:

"Hola" + " mundo"
'Hola mundo'
"Hola" + " " + "mundo"
'Hola mundo'

Otra manera de concatenar cadenas es utilizar el método join. Este método nos sirve para unir una lista de cadenas mediante un separador, por ejemplo:

primer_nombre = "Ana"
segundo_nombre = "Isabel"
separador = " "
separador.join( [primer_nombre, segundo_nombre] )
'Ana Isabel'
separador = "---"
separador.join( [primer_nombre, segundo_nombre] )
'Ana---Isabel'

La cantidad de cadenas a unir pueden ser más dos, observa el siguiente ejemplo:

", ".join(["Ana", "Jorge", "David", "José", "Juan"])
'Ana, Jorge, David, José, Juan'

Naturalmente, el separador puede ser cualquier caracter válido, incluyendo algunos poco usuales:

" \U0001F970 ".join(["Ana", "Jorge", "David", "José", "Juan"])
'Ana 🥰 Jorge 🥰 David 🥰 José 🥰 Juan'

4.2. Indexación y slicing

Las cadenas de caracteres son secuencias de elementos, donde cada elemento corresponde a un caracter específico. Cada elemento de la cadena tiene asociado un índice, el cual corresponde a la posición en que se encuentran, y por lo tanto serán enteros positivos. La numeración de los índices comienza en cero. En la figura se muestra una cadena de caracteres guardada en la variable nombre, podemos observar que a cada caracter le corresponde un índice. Por ejemplo, a la letra C le corresponde el índice 0, a la letra t el índice 2.

Se puede acceder a cada una de los símbolos que componen una cadena mediante la notación cadena[idx], donde cadena es el nombre de la cadena e idx el índice en que se encuentra el caracter al cual se desea acceder, siendo 0 para la primera letra, 1 para la segunda y así de manera consecutiva. Veamos el ejemplo descrito en la figura, primero creamos la cadena nombre:

nombre = "Catalina"

Si quisiéramos acceder a la letra C tendríamos que utilizar el índice 0, es decir:

nombre[0]
'C'

Para acceder a la letra t utilizaríamos el índice 2:

nombre[2]
't'

En las secuencias se pueden utilizar también índices negativos para acceder a los elementos. En este caso, el último elemento siempre tendrá asociado el índice -1 y a partir de ahí hacia la izquierda el índice de cada elemento adyacente disminuye una unidad, es decir, \(-1, -2, -3, -4, ..., -n\), donde \(n\) es el número de elementos de la secuencia. En la siguiente figura podemos observar los índices negativos asociados a cada elemento de la cadena almacenada en la variable nombre.

Así, si quisiéramos acceder al último elemento de dicha cadena, podríamos hacerlo utilizando el índice -1:

nombre[-1]
'a'

Es importante tener en cuenta que una cadena de caracteres no sólo está compuesta de símbolos alfanuméricos, sino también signos de puntuación o espacios en blanco o saltos de línea, etc. Vamos a crear una variable llamada frase, en la cual guardamos una cadena compuesta por dos palabras, separadas por un espacio.

frase = "Hola. Adiós."

Si accedemos al elemento en el índice 4, podemos observar que este corresponde al punto:

frase[4]
'.'

Ahora, si accedemos al elemento en el índice 5, veremos que el elemento correspondiente es el espacio:

frase[5]
' '

Ahora vamos a revisar una manera de acceder a una porción de una secuencia, no a un sólo elemento como lo hacemos con la indexación. Habitualmente se denomina slicing a este tipo de operación. Con la sintaxis cadena[a:b] podemos acceder al conjunto de caracteres comprendidos entre los índices a y b-1.

Observa la siguiente línea, la cual nos permite tomar los elementos con índices 0 y 1:

nombre[0:3] # Elementos 0, 1 y 2
'Cat'

Esta otra línea nos permite acceder a los elementos con índices 2, 3, 4, 5 y 6:

nombre[3:7] # elementos 3, 4, 5 y 6
'alin'

Cómo ya habrás notado la cuestión es simple e intuitiva, sólo debemos tener cuidado con la notación y recordar que no se incluye el elemento dado por el índice superior, sino hasta el correspondiente al índice inmediatamente anterior a este. En la siguiente figura puedes observar una representación gráfica de las dos operaciones de slicing anteriores.

Cuando se prescinde de uno de los índices en la notación de slicing, se asume que se toman todos los valores desde el inicio hasta el índice indicado menos uno; o bien desde el índice establecido hasta el final de la cadena; esto dependende, obviamente, del índice del cual se prescinda.

Por ejemplo, la siguiente línea toma los elementos desde el inicio de la cadena hasta el índice 4-1:

nombre[:4]
'Cata'

Esta otra línea toma los elementos desde el índice 4 hasta el final de la cadena.

nombre[4:]
'lina'

La siguiente imagen muestra una representación gráfica de lo descrito anteriormente.

De una cadena de caracteres también pueden obtenerse un conjunto de elementos sin tomarlos de uno en uno, sino cada dos, cada tres, etc. Si utilizamos la notación cadena[a:b:n], nos devolverá el conjunto de elementos comprendidos entre los índices a y b-1, tomados cada n elementos. Observa el siguiente ejemplo:

nombre = "Agustín"
nombre[0:4:2]
'Au'

La línea anterior nos devuelve los elementos ubicados en los índices \(0, 1, 2 \, y \, 3\), pero de estos únicamente los toma a cada dos elementos, es decir:

  • Comenzamos tomando el elemento de índice \(0\) (letra A),

  • Ignoramos el elemento de índice \(1\) (letra g)

  • Tomamos el elemento de índice \(2\) (letra u)

  • Ignoramos el elemento de índice \(3\) (letra s)

En este punto se han agotado todos los elementos comprendidos entre los límites indicados, podemos observar que los únicos elementos tomados y devueltos por la instrucción nombre[0:4:2] son la letra A y u, tal como se mostraba en la línea anterior. En la siguiente figura se muestra un esquema gráfico de esta operación.

Veamos ahora otro ejemplo con la misma cadena:

nombre[3:7:3]
'sn'

Observa que en este caso tomamos los elementos ubicados en los índices \(3, 4, 5 \, y \, 6\), pero tomados cada tres elementos, es decir:

  • Tomamos el elemento ubicado en el índice 3 (letra s)

  • Ignoramos el elemento ubicado en el índice 4 (letra t)

  • Ignoramos el elemento ubicado en el índice 5 (letra í)

  • Tomamos el elemento ubicado en el índice 6 (letra n)

Una representación gráfica de esta operación la puedes observar en la siguiente figura.

Si quisiéramos tomar todos los elementos de una cadena a cada dos, podríamos hacer lo siguiente:

nombre[::2]
'Autn'

Una manera muy sencilla de invertir el orden de los elementos en una cadena de texto es utilizando esta notación de slicing, pero utilizando un valor negativo. Observa lo siguiente:

nombre[::-1]
'nítsugA'

Puedes notar rápidamente que se toman todos los elementos de la cadena, pero comenzando desde el final con un paso negativo. Lo mismo se podría hacer pero tomando a cada dos elementos, de la siguiente manera:

nombre[::-2]
'ntuA'

4.3. Mayúsculas y minúsculas

Cuando se trabaja con texto en ocasiones puede ser necesario hacer modificaciones en lo que corresponde a la presencia de mayúsculas y minúsculas. Podríamos por ejemplo tener de entrada un texto completamente en mayúsculas y convertirlo en minúsculas, o el caso contrario. En Python las cadenas de caracteres disponen de los métodos upper y lower que nos permiten convertir en mayúsculas y minúsculas, de manera respectiva, todas las letras que conforman una cadena de texto.

Vamos a definir una variable frase en la cual guardaremos la siguiente cadena:

frase = "Hola mundo"

Si quisiéramos convertir toda la cadena en mayúsculas utilizamos el método upper:

frase.upper()
'HOLA MUNDO'

Si por el contrario quisiéramos que todas las letras fueran minúsculas utilizamos el método lower:

frase.lower()
'hola mundo'

Debes tener cuidado y considerar que al momento de utilizar los métodos upper y lower, estos no modifican a la cadena de caracteres almacenada en frase, sino que devuelven una nueva cadena con las modificaciones realizadas. Esto se debe a que las cadenas en Python son objetos inmutables y una vez creadas no pueden modificarse. Observa que si en este punto imprimimos la variable frase tendríamos exactamente la misma cadena (sin modificar) definida inicialmente:

print(frase)
Hola mundo

Si quisieras modificar la cadena almacenada en la variable frase, entonces tendrías que hacer una reasignación, como se muestra en el siguiente ejemplo:

frase = frase.upper()
print(frase)
HOLA MUNDO

Podemos observar que ahora la variable frase almacena la versión en mayúsculas de la cadena.

Otro método que puede resultarte de utilidad es capitalize, el cual te permite colocar la primera letra en mayúsculas y todas las demás en minúsculas, como habitualmente ocurre con una oración. Observa el ejemplo siguiente:

texto = "mi perro es color bermejo"
texto.capitalize()
'Mi perro es color bermejo'

4.4. Formateo de cadenas de caracteres

En el contexto de este capítulo entenderemos el formateo de una cadena de caracteres como las operaciones o instrucciones que permiten obtener una cadena final a partir de la unión o sustitución de un conjunto de datos.

Previamente hemos aprendido que podemos concatenar cadenas, así por ejemplo si tuviéramos un programa que pide el nombre y apellidos del usuario, podríamos unir esa información mediante concatenación y generar, por ejemplo, un saludo personalizado, observemos:

nombre = input("Ingresa tu nombre: ")
apellidos = input("Ingresa tus apellidos: ")
saludo = "Hola " + nombre + " " + apellidos + ", bienvenid@."
print(saludo)
Ingresa tu nombre: Jorge
Ingresa tus apellidos: De Los Santos
Hola Jorge De Los Santos, bienvenid@.

Una manera más conveniente de lograr lo anterior es haciendo formateo de cadenas, a diferencia de la concatenación suele ser una forma más limpia y mantenible de generar cadenas en donde hay valores que van a cambiarse de forma dinámica dependiendo los datos que se generen durante la ejecución del código.

Por ejemplo lo anterior podría hacerse utilizando el método format de las cadenas de caracteres, de la siguiente manera:

nombre = input("Ingresa tu nombre: ")
apellidos = input("Ingresa tus apellidos: ")
saludo = "Hola {0} {1}, bienvenid@.".format(nombre, apellidos)
print(saludo)
Ingresa tu nombre: Jorge
Ingresa tus apellidos: De Los Santos
Hola Jorge De Los Santos, bienvenid@.

Los términos {0} y {1} son los marcadores de posición (placeholders) que indican que esa posición será ocupada por los argumentos pasados al método format, que en este caso son los valores almacenados en nombre y apellidos.

Otra manera de realizar lo anterior es utilizando los f-strings, observemos el siguiente código:

nombre = input("Ingresa tu nombre: ")
apellidos = input("Ingresa tus apellidos: ")
saludo = f"Hola {nombre} {apellidos}, bienvenid@."
print(saludo)
Ingresa tu nombre: Jorge
Ingresa tus apellidos: De Los Santos
Hola Jorge De Los Santos, bienvenid@.

Los f-strings son la manera más conveniente de formatear strings en Python, están disponibles desde la versión 3.6. Puedes observar que únicamente anteponemos una f a la definición de la cadena, esto nos permite utilizar marcadores de posición que hacen uso directamente del nombre de variables dentro del string, lo cual hace que se gane mucho en concisión y claridad. En lo subsiguiente vamos a describir las dos formas anteriores de formatear strings.

4.4.1. El método format

El método format permite dar formato a cadenas de caracteres, mediante la sintaxis:

string.format(arg1, arg2, ...)

Donde string es una cadena de caracteres que contiene marcadores de posición que serán sustituidos por los valores arg1, arg2, etc. Los marcadores de posición se definen utilizando llaves y dentro de ellas se puede colocar un número, un nombre o una expresión que permitirá formatear el valor pasado como argumento.

Veamos el siguiente ejemplo:

texto = "{} + {}"
print( texto.format(1,2) )
1 + 2

Observa que los dos pares de llaves se reemplazan por los valores numéricos pasados como argumentos, en ese mismo orden. Si quisiéramos controlar explícitamente el orden de aparición dentro de la cadena podríamos utilizar marcadores de posición con un consecutivo (índice), por ejemplo:

texto = "{1} + {0}"
print( texto.format(1,2) )
2 + 1

Lo anterior indica que {0} se sustituirá por el primer argumento pasado al método format, {1} por el segundo argumento y así de manera consecutiva en el caso de que hubiera más argumentos.

Es posible también utilizar argumentos nombrados para el método format e indicarlos con dicho en el marcador de posición, observa lo siguiente:

texto = "{apellidos}, {nombre}"
print( texto.format(nombre="Juan", apellidos="Pérez López") )
Pérez López, Juan

En algunas situaciones cuando trabajamos con números es muy probable que necesitemos mostrar los resultados con cierta cantidad de decimales o cifras significativas y/o en una notación específica, con format tenemos algunas posibilidades. Por ejemplo, vamos a mostrar el valor de \(pi\) con algunos decimales:

texto = "{pi}"
print(texto.format(pi=3.14159265359))
3.14159265359

Si quisiéremos mostrar únicamente cuatro decimales podríamos hacerlo de la siguiente manera:

texto = "{pi:.4f}"
print(texto.format(pi=3.14159265359))
3.1416

Observa que colocamos dos puntos y enseguida un especificador de formato .4f, este especificador le indica al método format que únicamente queremos mostrar cuatro decimales. Si quisiéramos mostrar seis decimales utilzaríamos .6f, y así con cualquier otra cantidad. En lo anterior f es el indicativo de un formateo de punto fijo.

Podemos también mostrar un número utilizando notación científica, para ello utilizamos el especificador de formato E o e. En las calculadores se suele utilizar el caracter E (o alguno similar) para indicar 10 elevado a una potencia, por ejemplo 3.5E5 sería el equivalente de \(3.5 \times 10^5\). Veamos como funciona:

fuerza = 7500
area = 0.08 * 0.03 
esfuerzo = fuerza / area
texto = "El esfuerzo normal es de {0:E} Pa"
print( texto.format(esfuerzo) )
El esfuerzo normal es de 3.125000E+06 Pa

Podemos controlar también el número de decimales a mostrar:

fuerza = 7500
area = 0.08 * 0.03 
esfuerzo = fuerza / area
texto = "El esfuerzo normal es de {0:.2E} Pa"
print( texto.format(esfuerzo) )
El esfuerzo normal es de 3.13E+06 Pa
print( "{0:.2%}".format(0.75) )
75.00%

4.4.2. Los f-strings

A partir de la versión 3.6 de Python podemos hacer uso de los f-strings para formatear cadenas de caracteres de una forma mucho más conveniente. Con los f-strings podemos sustituir directamente en la cadena el valor de una variable o el resultado de un conjunto de operaciones, observa lo siguiente:

a = 10
b = 20
print(f"{a} + {b} = {a + b}")
10 + 20 = 30

Primero, debemos notar que para que Python pueda interpretar que estamos creando un f-string debemos anteponerle una f a la definición de la cadena de caracteres. Observa que los nombres de las variables encerrados entre llaves {a} y {b} se sustituyen por el valor guardado en cada variable, la expresión {a + b} nos permite evaluar una operación de suma aritmética entre los dos valores numéricos almacenados en a y b y sustituir el resultado en la cadena.

Las especificaciones de formato descritas para el método format siguen siendo válidas en los f-strings.

fuerza = 7500
area = 0.08 * 0.03 
esfuerzo = fuerza / area
texto = f"El esfuerzo normal es de {esfuerzo:.2E} Pa"
print( texto )
El esfuerzo normal es de 3.13E+06 Pa

Naturalmente, es mucho más sencillo (tanto para la escritura, lectura como para el mantenimiento del código) sustituir directamente el nombre de una variable dentro de la cadena de caracteres.

Como ya se mencionaba, en los f-strings podemos sustituir el resultado de una operación o de una instrucción. Observa el siguiente ejemplo en el cual se muestra la equivalencia en el sistema binario de un entero expresado en el sistema decimal:

n = int( input("Inserta un entero: ") )
print( f"El número {n} en binario es {bin(n)}" )
Inserta un entero: 12
El número 12 en binario es 0b1100

Podemos observar que dentro del f-string se hace una invocación a la función bin, la cual es una función nativa de Python que recibe un entero y devuelve su representación en el sistema binario. Lo anterior también podríamos lograrlo sin necesidad de hacer uso de la función bin, sino en su lugar modificar los especificadores de formato, tal como se muestra enseguida:

n = int( input("Inserta un entero: ") )
print( f"El número {n} en binario es {n:b}" )
Inserta un entero: 12
El número 12 en binario es 1100

El modificador de formato :b indica que el valor pasado en el placeholder se debe mostrar en formato binario.

4.5. Buscando en una cadena de caracteres

En este apartado vamos a revisar cómo buscar una cierta secuencia de caracteres dentro de una cadena. Buscar coincidencias en un texto suele ser una tarea muy común para algunos sistemas automatizados. En Python, las cadenas de caracteres poseen métodos que permiten buscar en ellas coincidencias.

Vamos a crear una variable frase, en la cual almacenaremos una cadena de caracteres:

frase = "La estrella más cercana es Próxima Centauri"

Si quisiéramos saber cuántas veces aparece en esa frase una cierta letra, entonces podríamos utilizar el método count. Este método simplemente recibe como argumento la secuencia de caracteres que queremos buscar y nos devuelve la cantidad de veces que dicha secuencia de caracteres aparece, tal como se muestra enseguida:

frase.count("a")
6

Observa que en la línea anterior estamos buscando cuántas veces aparece la letra a dentro de frase. Si contamos manualmente, podrás observar que el método count está ignorando la letra á con tilde. Naturalmente, Python no puede saber, al menos en principio, que á es también una letra a, pero con la particularidad escrita de la tilde. Una manera bastante simplista de considerar en la contabilización a la letra á tildada sería como sigue:

frase.count("a") + frase.count("á")
7

Con el método count no solamente podemos contabilizar el número de veces que aparece un caracter, sino también cualquier secuencia de caracteres. Observa el siguiente ejemplo:

texto = """Los amorosos callan.
El amor es el silencio más fino,
el más tembloroso, el más insoportable.
Los amorosos buscan,
los amorosos son los que abandonan,
son los que cambian, los que olvidan.
"""
texto.count("silencio")
1

Con la línea anterior buscamos la cadena silencio dentro del texto creado. Hay que ser un poco precavidos cuando utilizamos el método count, veamos el siguiente caso:

texto.count("amor")
4

De acuerdo con lo anterior la secuencia de caracteres amor aparece cuatro veces. Si observas el texto verás que la palabra amor aparece una sola vez, sin embargo la subcadena amor forma parte también de la palabra amorosos, la cual aparece tres veces. Si quisiéramos únicamente contabilizar la palabra amor, como tal, tendríamos que añadir los espacios en blancos al inicio y final de la palabra, es decir, algo como lo siguiente:

texto.count(" amor ")
1

En el caso de que únicamente nos interese saber si una subcadena forma parte de un texto, podemos utilizar el operador in. De manera general, el operador in nos permite verificar si un determinado elemento está presente en una secuencia. Observa el siguiente ejemplo:

texto = """
La ley de la gravedad de Newton nos dice también que cuanto más separados estén
los cuerpos menor será la fuerza gravitatoria entre ellos.
"""
"Newton" in texto
True
"Dirac" in texto
False

Podemos verificar que si la palabra (o secuencia de caracteres) que buscamos está contenida en la variable texto, entonces se devuelve un valor lógico True, de lo contrario devolverá un False, tal como se puede constatar al momento que buscamos la cadena Dirac dentro de texto.


Ejemplo. Contando vocales en una palabra

En este ejemplo vamos a desarrollar un programa que dada una palabra nos devuelva la cantidad vocales totales que hay en esta.

palabra = "Perro"

ka = palabra.count("a") # cantidad de letras a
ke = palabra.count("e") # cantidad de letras e
ki = palabra.count("i") # cantidad de letras i
ko = palabra.count("o") # cantidad de letras o
ku = palabra.count("u") # cantidad de letras u

numero_de_vocales = ka + ke + ki + ko + ku

print(f"En la palabra '{palabra}' hay {numero_de_vocales} vocales")
En la palabra 'Perro' hay 2 vocales

4.6. Una breve introducción a las expresiones regulares

Las expresiones regulares son patrones que se utilizan para hacer coincidir combinaciones de caracteres en cadenas 1. Son una herramienta poderosa para buscar, analizar y modificar texto. En general, las expresiones regulares permiten:

  • Búsqueda: encontrar coincidencias de un patrón en un texto.

  • Extracción: obtener partes específicas de un texto que coincidan con un patrón.

  • Reemplazo: cambiar partes de un texto por otras según un patrón.

  • Validación: verificar si un texto cumple con un formato específico.

Las expresiones regulares se componen de una serie de caracteres especiales que tienen un significado específico. Estos caracteres se pueden combinar para crear patrones complejos. Algunos de los caracteres más comunes son:

  • . (punto): coincide con cualquier carácter.

  • []: (corchetes): coincide con cualquier carácter dentro del rango especificado.

  • {}: (llaves): indica la cantidad de veces que debe repetirse el caracter anterior.

  • ? (signo de interrogación): indica que el caracter anterior es opcional.

  • *: (asterisco): indica que el caracter anterior puede repetirse 0 o más veces.

  • +: (signo más): indica que el caracter anterior puede repetirse 1 o más veces.

import re
texto = "Hoy es 12/11/2021 o bien 07/03/21 o 12-dic-2020"
re.findall("(\d{2}/\d{2}/\d{2,4}|\d{2}-\w{3}-\d{2,4})", texto)
['12/11/2021', '07/03/21', '12-dic-2020']
re.finditer("(\d{2}/\d{2}/\d{2,4}|\d{2}-\w{3}-\d{2,4})", texto)
<callable_iterator at 0x2d8dc81ecd0>

4.7. Ejercicios


Escriba un programa que determine si una palabra es un palíndromo. Deberá mostrar Es palíndromo en el caso de que lo sea y No es palíndromo en el otro caso.


Desarolle un programa que dada una frase, reemplace todas las vocales que contiene por cada una de las vocales. Como en esa canción infantil de El sapo no se lava el pie. Así por ejemplo, si la frase es el sapo no se lava el pie, entonces el programa deberá mostrar:

al sapa na sa lava al paa
el sepe ne se leve el pee
il sipi ni si livi il pii
ol sopo no so lovo ol poo
ul supu nu su luvu ul puu

1

https://www.calculo.jcbmat.com/id454.htm