Cadenas de caracteres y algoritmos aplicados
Presentación
1. Definición
Una cadena de caracteres es una colección ordenada y modificable de caracteres. No existe, necesariamente, una noción de duplicados, puesto que esta noción no tiene sentido para una cadena de caracteres. El orden es importante, pues se trata del orden de lectura, sin el cual no tiene sentido.
En Python 3, los métodos disponibles son:
>>> dir(str)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__',
'__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'capitalize', 'center', 'count', 'encode',
'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index',
'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier',
'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle',
'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans',
'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition',
'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip',
'swapcase', 'title', 'translate', 'upper', 'zfill']
Podemos considerar que una cadena de caracteres es una secuencia que contiene únicamente caracteres. Las cadenas de caracteres son, así, comparables entre sí, direccionables mediante índices y también mediante tramos:
>>> sorted(set(dir(str))&set(dir(list)))
['__add__', '__class__'...
Dar formato a cadenas de caracteres
1. Operador módulo
Las cadenas de caracteres tienen una implementación específica del operador módulo. Esta realiza lo mismo que el lenguaje C hace con printf, aunque la funcionalidad proporcionada es todavía más impresionante.
Tras el módulo no puede haber más de un objeto. Si se necesita pasar varios, es preciso utilizar una n-tupla, usando paréntesis para definir las prioridades.
>>> 'esto es %s' % 'una cadena'
'esto es una cadena'
>>> '%s es %s' % ('esto', 'una cadena')
'esto es una cadena'
>>> '%s es %s' % 'esto', 'una cadena'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: not enough arguments for format string
Veamos los distintos formatos posibles.
Simplificaremos utilizando %s:
>>> '%s' % 'cadena'
'cadena'
>>> '%s' % 1
'1'
>>> '%s' % 1.0
'1.0'
>>> '%s' % 1.000
'1.0'
>>> '%s' % 1.
'1.0'
>>> '%s' % str
"<class 'str'>"
La conversión se realiza con ayuda del método __str__ de cada objeto y es posible referirse al método __repr__ utilizando %r en lugar de %s.
Por orden de frecuencia de uso, vienen los números enteros:
>>> '%d' % 1
'1'
>>> '%d' % 1.0
'1'
>>> '%d' % 1.9
'1'
El formato puede ser mucho más preciso, y ofrece la posibilidad de gestionar el tamaño de la cadena de salida de modo que, por ejemplo, se alineen las cifras tras la escritura de varias líneas consecutivas para gestionar, así, una alineación que favorezca la lectura del resultado producido:
>>> '%3d' % 4
' 4'
>>> '%-3d' % 4
'4 '
La cifra 3 en %3d representa la longitud mínima de la cadena, que puede superarse si la cifra es demasiado grande, puesto que el valor es más importante que el formato (el fondo es más importante...
Operaciones de conjunto
1. Secuenciación de cadenas
Una cadena de caracteres es, como se ha visto, iterable y puede utilizarse como una lista, en ciertos aspectos:
>>> for c in 'char':
... print(c)
...
c
h
a
r
Si es necesario, resulta sencillo transformar una cadena de caracteres en una lista de caracteres:
>>> s = 'cadena de caracteres'
>>> l = list(s)
>>> l
['c', 'a', 'd', 'e', 'n', 'a', ' ', 'd', 'e', ' ', 'c', 'a', 'r',
'a', 'c', 't', 'e', 'r', 'e', 's']
Dicha operación es raramente útil, puesto que el tipo de datos cadena de caracteres dispone de todas las opciones que puede necesitar un desarrollador, aunque la conversión sigue siendo posible.
Otra opción es convertirla en un conjunto:
>>> e = set(s)
>>> s
'cadena de caracteres'
>>> e
{'a', ' ', 'c', 'e', 'd', 'n', 's', 'r', 't'}
Gracias a esta herramienta podemos conocer la lista de las letras que se usan en una cadena y, utilizando matemáticas de conjunto, comparar dicha lista con otra cadena y saber qué comparten:
>>> s2 = "otra cadena"
>>> e2 = set(s2)
>>> e2
{'a', ' ', 'c', 'd', 'e', 'n', 'o', 'r', 't'}
>>> e | e2
{'a', ' ', 'c', 'd', 'e', 'n', 'o', 's', 'r', 't'}
>>> e & e2
{'a', ' ', 'c', 'd', 'e', 'n', 'r', 't'}
>>> e ^ e2
{'o', 's'}
>>> e - e2
{'s'}
>>> e2 - e
{'o'}
Esto permite trabajar a nivel de carácter...
Problemáticas relativas a la codificación
1. Codificación por defecto
En Python 2.x, la codificación por defecto es ASCII:
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
En Python 3.x, es UTF-8:
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
2. Codificación del sistema
En Python 2.x, la codificación del sistema es conocida:
>>> import sys
>>> sys.getfilesystemencoding()
'UTF-8'
En Python 3.x, no se representa exactamente de la misma forma, aunque es la misma:
>>> import sys
>>> sys.getfilesystemencoding()
'utf-8'
En ambos casos, se reconoce correctamente.
3. Unicode, referencia absoluta
Unicode es una codificación que se ha creado con la finalidad de remplazar las codificaciones regionales, nacionales o semicontinentales para englobar en su seno todos los caracteres utilizados por las demás codificaciones.
Esto significa que las demás codificaciones son subconjuntos y que, en consecuencia, resulta muy sencillo pasar de Unicode a otra codificación, sea un conjunto grande o pequeño.
Existen varias codificaciones similares entre sí o que comparten en su tabla caracteres comunes. Las normas son numerosas y, en ocasiones, fuente de confusión, pues muchas han evolucionado en la historia de la informática, vinculadas a la evolución de la historia humana (aparición del símbolo €, por ejemplo), al hardware y a problemáticas de bajo nivel.
El resultado de la codificación de una cadena Unicode da como resultado un objeto de tipo bytes:
>>> test = "ejemplo de codificación"
>>> test.encode('latin1')
b'ejemplo de codificaci\xc3\xb3n'
>>> test.encode('iso-8859-1')
b'ejemplo de codificaci\xf3n'
Cuando el conjunto contiene todos los caracteres necesarios para realizar la conversión...
Manipulaciones de bajo nivel avanzadas
1. Operaciones para contar
He aquí una cadena de caracteres que representa un texto, de modo que puede almacenarse de forma persistente y recuperarse según los procedimientos habituales.
>>> s='''Esta es una frase corta. Esta es una un poco
más larga.
... Esto es otro párrafo.
... Esto es el último párrafo.
... '''
Contar el número de símbolos es trivial:
>>> len(s)
132
Contar el número de frases resulta algo más delicado:
>>> len(s.split('.'))
4
Es preciso tener en cuenta todos los caracteres de puntuación al final de las frases. La solución no es demasiado buena, pues crea tablas con tantas dimensiones como caracteres y obliga a realizar iteraciones algo pesadas a la vez en la sintaxis y en el procesamiento:
>>> temp=[a.split('!') for a in s.split('.')]
>>> frases=[]
>>> for tmp in temp:
... frases.extend(tmp)
...
>>> len(frases)
5
Un algoritmo similar que gestione simultáneamente el punto, el signo de interrogación, el signo de exclamación, los puntos suspensivos, los dos puntos y el punto y coma sería también largo de escribir y de ejecutar. La solución...
Representación en memoria
1. Presentación del tipo bytes
Ahora que una cadena de caracteres se representa únicamente por el tipo unicode, el tipo bytes no se ve más que como una representación de bits, de bytes o de valores hexadecimales, y se utiliza específicamente para problemáticas de bajo nivel.
De este modo, al convertir una cadena de caracteres en un juego de caracteres diferente a Unicode, se considera como una serie de bits. Del mismo modo, un valor entero se ve como una serie de bits.
He aquí una forma de visualizar cómo se representan los 256 primeros números enteros en bytes, en paralelo con su representación octal y hexadecimal:
>>> def int_and_bytes():
... print('+-----+---------+-------+------+')
... print('| int | bytes | octal | hexa |')
... print('+-----+---------+-------+------+')
... for i in range(256):
... print('| %3d | %-7s | %#-5o | %#-4x |' % (i,
i.to_bytes(1, 'big'), i, i))
... print('+-----+---------+-------+------+')
...
El resultado de este script se muestra en el anexo.
Es posible transformar cualquier valor entero en bytes siempre y cuando la longitud de la representación sea suficiente:
>>> (10000).to_bytes(4, 'big')
b"\x00\x00'\x10"
>>> (10000).to_bytes(2, 'big')
b"'\x10"
>>> (10000).to_bytes(1, 'big')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: int too big to convert
Además de la longitud de la representación, es preciso tener en cuenta la terminación:
>>> (12345).to_bytes(4, 'big')
b'\x00\x0009'
>>> (12345).to_bytes(4, 'little')
b'90\x00\x00'
Es también importante saber si la representación incluye el signo, lo cual es posible utilizando un parámetro nombrado (de la simple lectura del código):
>>> (12345).to_bytes(4, 'big', signed=True)
b'\x00\x0009'
>>> (12345).to_bytes(4...