Estructuras de datos en Python: Listas

El tipo de datos “lista” tiene algunos métodos más. Éstos son todos los métodos de los objetos lista:

append(x): Añade un elemento al final de una lista; es equivalente a a[len(a):] = [x].

extend(L): Extiende la lista concatenándole todos los elementos de la lista indicada; es equivalente a a[len(a):] = L.

insert(i, x):Inserta un elemento en un posición dada. El primer argumento es el índice del elemento antes del que se inserta, por lo que a.insert(0, x) inserta al principio de la lista y a.insert(len(a), x) equivale a a.append(x).

remove(x):Elimina el primer elemento de la lista cuyo valor es x. Provoca un error si no existe tal elemento. pop([i]): Elimina el elemento de la posición dada de la lista y lo devuelve. Si no se especifica un índice, a.pop() devuelve el último elemento de la lista y también lo elimina. Los corchetes que rodean la i en la signatura del método indican que el parámetro es opcional, no que haya que teclear los corchetes en dicha posición. Esta notación es frecuente en la Referencia de las bibliotecas de Python.

index(x):Devuelve el índice del primer elemento de la lista cuyo valor sea x. Provoca un error si no existe tal elemento.

count(x):Devuelve el número de veces que aparece x en la lista.

sort():Ordena ascendentemente los elementos de la propia lista (la lista queda cambiada).

reverse():Invierte la propia lista (la lista queda cambiada).

Un ejemplo que utiliza varios métodos de la lista:

>>> a = [66.25, 333, 333, 1, 1234.5]
>>> print a.count(333), a.count(66.25), a.count('x')
2 1 0
>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.25, 333, -1, 333, 1, 1234.5, 333]
>>> a.index(333)
1
>>> a.remove(333)
>>> a
[66.25, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.25]
>>> a.sort()
>>> a
[-1, 1, 66.6, 333, 333, 1234.5]

Cómo usar las listas como pilas

Los métodos de las listas facilitan mucho usar una lista como una pila, donde el último elemento añadido es el primer elemento recuperado. (“last-in, first-out”, “último en llegar, primero en salir”). Para apilar un elemento, usa append(). Para recuperar el elemento superior de la pila, usa pop() sin un índice explícito. Por ejemplo:

>>> pila = [3, 4, 5]
>>> pila.append(6)
>>> pila.append(7)
>>> pila
[3, 4, 5, 6, 7]
>>> pila.pop()
7
>>> pila
[3, 4, 5, 6]
>>> pila.pop()
6
>>> pila.pop()
5
>>> pila
[3, 4]

Cómo usar las listas como colas

También es muy práctico usar una lista como cola, donde el primer elemento que se añade a la cola es el primero en salir (“first-in, first-out”, “primero en llegar, último en salir”). Para añadir un elemento al final de una cola, usa append(). Para recuperar el primer elemento de la cola, usa pop() con 0 de índice. Por ejemplo:

>>> cola = ["Eric", "John", "Michael"] 
>>> cola.append("Terry")           # llega Terry
>>> cola.append("Graham")          # llega Graham
>>> cola.pop(0)
'Eric'
>>> cola.pop(0)
'John'
>>> cola
['Michael', 'Terry', 'Graham']

Herramientas de programación funcional

Hay tres funciones internas que son muy útiles al tratar con listas: filter(), map() y reduce().

filter(función, secuencia)“, filtrar, devuelve una secuencia (del mismo tipo, si es posible) que contiene aquellos elementos de la secuencia de entrada para los que función(elemento) resulta verdadero. Por ejemplo, para calcular algunos primos:

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

map(función, secuencia)“, transformar, llama a función(elemento) para cada uno de los elementos de la secuencia y devuelve una lista compuesta por los valores resultantes. Por ejemplo, para calcular algunos cubos:

>>> def cubo(x): return x*x*x
...
>>> map(cubo, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

Se puede pasar más de una secuencia como parámetro. La función debe tener tantos argumentos como secuencias se le pasan y se llama a la función con el valor correspondiente de cada secuencia de entrada (o None si una secuencia es más corta que otra). Por ejemplo:

>>> secuencia = range(8)
>>> def suma(x,y): return x+y
...
>>> map(suma, secuencia, secuencia)
[0, 2, 4, 6, 8, 10, 12, 14]

reduce(func, secuencia)“,reducir, devuelve un valor simple que se construye llamando a la función binaria func con los dos primeros elementos de la secuencia, luego con el resultado y el siguiente elemento y así sucesivamente. Por ejemplo, para calcular la suma de los números de 1 a 10:

>>> def suma(x,y): return x+y
...
>>> reduce(suma, range(1, 11))
55

Si sólo hay un elemento en la secuencia, se devuelve su valor; si la secuencia está vacía, se lanza una excepción.

Se puede pasar un tercer argumento para indicar el valor inicial. En este caso, se devuelve este valor inicial para la secuencia vacía y la función se aplica al primer elemento, luego al segundo y así sucesivamente. Por ejemplo,

>>> def sum(secuencia):
...     def suma(x,y): return x+y
...     return reduce(suma, secuencia, 0)
... 
>>> sum(range(1, 11))
55
>>> sum([])
0

No uses la función sum() de este ejemplo: como sumar números es una tarea común, se proporciona una función interna sum(secuencia) que hace esto exactamente. New in version 2.3.

Listas autodefinidas

Las listas autodefinidas proporcionan un modo conciso de crear listas sin recurrir al uso de map(), filter() ni lambda. La definición de lista resultante tiende a ser más clara que las listas construidas con los métodos citados. Cada LC consta de una expresión seguida de una cláusula for y cero o más cláusulas for o if. La lista resultante se obtiene evaluando la expresión en el contexto de las cláusulas for e if que la siguen. Si la expresión debe dar como resultado una tupla, hay que encerrarla entre paréntesis.

>>> frutafresca = ['  plátano', '  mora ', 'fruta de la pasión  ']
>>> [arma.strip() for arma in frutafresca]
['plátano', 'mora', 'fruta de la pasión']
>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]
>>> [3*x for x in vec if x > 3]
[12, 18]
>>> [3*x for x in vec if x < 2]
[]
>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]]
>>> [x, x**2 for x in vec]  # error - se necesita un paréntesis en las tuplas
  File "<stdin>", line 1, in ?
    [x, x**2 for x in vec]
               ^
SyntaxError: invalid syntax
>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)]
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]
>>> [vec1[i]*vec2[i] for i in range(len(vec1))] 
[8, 12, -54] 
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]

Las listas autodefinidas son mucho más lexibles que map() y se pueden aplicar a funciones con más de un argumento y a funciones anidadas:

>>> [str(round(355/113.0, i)) for i in range(1,6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

La sentencia del

Hay un modo de eliminar un elemento de una lista dado su índice en lugar de su valor: la sentencia del. También se puede utilizar para eliminar cortes de una lista (lo que hacíamos antes asignando una lista vacía al corte). Por ejemplo:

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]

del se puede utilizar para eliminar variable completas:

>>> del a

Hacer referencia al nombre a a partir de aquí provoca un error (al menos hasta que se asigne otro valor al nombre). Veremos otros usos de del más adelante.

Tuplas y secuencias

Hemos visto que las listas y las cadenas tienen muchas propiedades en común, por ejemplo, el indexado y el corte. Son dos ejemplos de tipos de datos secuenciales. Como Python es un lenguaje en evolución, se pueden añadir otros tipos de datos de secuencia. Hay otro tipo de dato secuencial estándar: la tupla.

Una tupla consta de cierta cantidad de valores separada por comas, por ejemplo:

>>> t = 12345, 54321, '¡hola!'
>>> t[0]
12345
>>> t
(12345, 54321, '¡hola!')
>>> # Se pueden anidar tuplas:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, '¡hola!'), (1, 2, 3, 4, 5))

Como puedes observar, en la salida se encierran las tuplas entre paréntesis, para que las tuplas anidadas se interpreten correctamente. En la entrada los paréntesis son opcionales, aunque a menudo son necesarios (si la tupla es parte de una expresión más compleja).

Las tuplas son muy útiles: Por ejemplo, pares de coordenadas (x,y), registros de empleados de una base de datos, etc. Las tuplas, como las cadenas, son inmutables: No es posible asignar un valor a los elementos individuales de una tupla (sin embargo, se puede simular el mismo efecto mediante corte y concatenación). También es posible crear tuplas que contengan objetos mutables, por ejemplo, listas.

Un problema especial es la construcción de tuplas de 0 ó 1 elementos: La sintaxis tiene trucos para resolver esto. Las tuplas vacías se construyen mediante un par de paréntesis vacío y las tuplas de un solo elemento se construyen mediante el valor del elemento seguido de coma (no vale con encerrar el valor entre paréntesis). Es feo, pero funciona. Por ejemplo:

>>> vacio = ()
>>> singleton = 'hola',    # <-- Observa la coma final
>>> len(vacio)
0
>>> len(singleton)
1
>>> singleton
('hola',)

La sentencia t = 12345, 54321, '¡hola!' es un ejemplo de empaquetado de tuplas: los valores 12345, 54321 y '¡hola!' se empaquetan en una tupla. También se puede realizar la operación inversa:

>>> x, y, z = t

Esto se llama, por supuesto, desempaquetado de secuencias. El desempaquetado de secuencias requiere que el número de variables sea igual al número de elementos de la secuencia. Observa que la asignación múltiple sólo es un efecto combinado del empaquetado de tuplas y desempaquetado de secuencias.

Esto resulta un poco asimétrico, ya que el empaquetado de varios valores siempre resulta en una tupla, aunque el desempaquetado funciona para cualquier secuencia.

Conjuntos

Python también incluye un tipo de dato para los conjuntos. Un conjunto es una colección desordenada sin elementos duplicados. Los usos básicos incluyen la comprobación de pertenencia y la eliminación de elementos duplicados. Los objetos conjunto disponen también de operaciones matemáticas como la unión, intersección, diferencia y diferencia simétrica.

He aquí una breve demostración:

>>> cesta = ['manzana', 'naranja', 'manzana', 'pera', 'naranja', 'banana']
>>> frutas = set(cesta)               # crea un conjunto sin duplicados
>>> frutas
set(['naranja', 'pera', 'manzana', 'banana'])
>>> 'naranja' in frutas                 # comprobación rápida de pertenencia
True
>>> 'ortigas' in frutas
False

>>> # Demostración de las operaciones de conjuntos sobre las letras de dos palabras

>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # letras diferentes de a
set(['a', 'r', 'b', 'c', 'd'])
>>> a - b                              # letras de a que no están en b
set(['r', 'd', 'b'])
>>> a | b                              # letras que están en a o b
set(['a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'])
>>> a & b                              # letras que están en a y también en b
set(['a', 'c'])
>>> a ^ b                              # letras que están en a y b pero no en los dos
set(['r', 'd', 'b', 'm', 'z', 'l'])

Diccionarios

Otro tipo de dato interno de Python que resulta útil es el diccionario. Los diccionarios aparecen a veces en otros lenguajes como “memorias asociativas” o “matrices asociativas”. A diferencia de las secuencias, que se indexan mediante un rango de números, los diccionarios se indexan mediante claves, que pueden ser de cualquier tipo inmutable. Siempre se puede utilizar cadenas y números como claves. Las tuplas pueden usarse de claves si sólo contienen cadenas, números o tuplas. Si una tupla contiene cualquier objeto mutable directa o indirectamente, no se puede usar como clave. No se pueden utilizar las listas como claves, ya que las listas se pueden modificar, por ejemplo, mediante el método append() y su método extend(), además de las asignaciones de corte y asignaciones aumentadas.

Lo mejor es pensar en un diccionario como en un conjunto desordenado de parejas clave: valor, con el requisito de que las claves sean únicas (dentro del mismo diccionario). Una pareja de llaves crea un diccionario vacío: {}. Si se coloca una lista de parejas clavevalor entre las llaves se añaden parejas clavevalor iniciales al diccionario. Así es como se presentan los diccionarios en la salida (hay ejemplos dentro de poco).

Las operaciones principales sobre un diccionario son la de almacenar una valor con una clave dada y la de extraer el valor partiendo de la clave. También se puede eliminar una pareja clavevalor con del. Si se introduce una clave que ya existe, el valor anterior se olvida. Intentar extraer un valor utilizando una clave no existente provoca un error.

El método keys() de un objeto de tipo diccionario devuelve todas las claves utilizadas en el diccionario, en orden arbitrario (si deseas ordenarlas, aplica el método sort() a la lista de claves). Para comprobar si una clave existe en el diccionario, utiliza el método has_key() del diccionario.

He aquí un pequeño ejemplo que utiliza un diccionario:

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> tel.keys()
['guido', 'irv', 'jack']
>>> tel.has_key('guido')
True

El constructor dict() construye diccionarios directamente a partir de listas de parejas clave-valor guardadas como tuplas. Cuando las parejas cumplen un patrón, se puede especificar de manera compacta las lista de parejas clave-valor.

>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}
>>> dict([(x, x**2) for x in (2, 4, 6)])     # uso de lista autodefinida
{2: 4, 4: 16, 6: 36}

Técnicas para hacer bucles

Al recorrer diccionarios, es posible recuperar la clave y su valor correspondiente a la vez, utilizando el método iteritems().

>>> caballeros = {'gallahad': 'el casto', 'robin': 'el valeroso'}
>>> for k, v in caballeros.iteritems():
...     print k, v
...
gallahad el casto
robin el valeroso

Al recorrer una secuencia, se pueden recuperar a la vez el índice de posición y su valor correspondiente usando la función enumerate().

 
>>> for i, v in enumerate(['pim', 'pam', 'pum']):
...     print i, v
...
0 pim
1 pam
2 pum

Para recorrer dos o más secuencias en paralelo, se pueden emparejar los valores con la función zip().

>>> preguntas = ['nombre', 'misión', 'color favorito']
>>> respuestas = ['lanzarote', 'el santo grial', 'azul']
>>> for p, r in zip(preguntas, respuestas):
...     print '¿Cuál es tu %s? %s.' % (p, r)
...
¿cuál es tu nombre? lanzarote.
¿cuál es tu misión? el santo grial.
¿cuál es tu color favorito? azul.
	<

Para recorrer una secuencia en orden inverso, hay que especificar primero la secuencia en el orden original y llamar a la función reversed().

>>> for i in reversed(xrange(1,10,2)):
...     print i
...
9
7
5
3
1

Para recorrer una secuencia en orden, usa la función sorted(), que devuelve una lista ordenada nueva, dejando intacta la secuencia original.

>>> cesta = ['manzana', 'naranja', 'manzana', 'naranja', 'pera', 'banana']
>>> for f in sorted(set(cesta)):
...     print f
... 	
banana
manzana
naranja
pera

Más sobre las condiciones

Las condiciones utilizadas en construcciones while e if descritas anteriormente pueden contener cualquier operador, no sólo comparaciones.

Los operadores de comparación in (dentro de) y not in (no dentro de) comprueban si un valor está incluido (o no) en una secuencia. Los operadores is (es) y is not (no es) comprueban si dos objetos son en realidad el mismo. Esto sólo tiene importancia en los objetos mutables, como las listas. Todos los operadores de comparación tienen la misma prioridad, que es menor que la de los operadores numéricos.

Se pueden encadenar las comparaciones: Por ejemplo, a < b == c comprueba si a es menor que b y además si b es igual a c.

Las comparaciones se pueden combinar mediante los operadores lógicos and (y) y or (o) y la salida de una comparación (o cualquier otra expresión lógica) se puede negar mediante not (no). Todos éstos tienen menor prioridad que los operadores de comparación. Entre ellos, not tiene la prioridad más elevada y or la más baja, por lo que A and not B or C equivale a (A and (not B)) or C. Como siempre, se pueden utilizar paréntesis para expresar un orden de operación concreto.

Los operadores lógicos and y or se denominan operadores de cortocircuito: Sus argumentos se evalúan de izquierda a derecha y la evaluación se interrumpe tan pronto como queda determinado el valor de salida. Por ejemplo, si A tiene un valor de verdadero pero B es falso, A and B and C no evalúa el valor de la expresión C. En general, el valor devuelto por un operador de atajo, cuando se utiliza como valor en general y no como valor lógico, es el último argumento evaluado.

Es posible asignar el resultado de una comparación u otra expresión lógica a una variable. Por ejemplo:

>>> cadena1, cadena2, cadena3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = cadena1 or cadena2 or cadena3
>>> non_null
'Trondheim'

Observa que en Python, al contrario que en C, no puede haber asignación dentro de una expresión. Los programadores en C pueden quejarse de esto, pero evita una causa común problemas hallados en los programas en C: teclear = en una expresión en la que se quería decir ==.

Comparación entre secuencias y otros tipos

Los objetos de secuencia se pueden comparar con otros objetos del mismo tipo de secuencia. La comparación utiliza ordenación lexicográfica: Primero se comparan los dos primeros elementos, si estos difieren ya está determinado el valor de la comparación, si no, se comparan los dos elementos siguientes de cada secuencia y, así sucesivamente, hasta que se agota alguna de las dos secuencias. Si alguno de los elementos que se compara es él mismo una secuencia, se lleva a cabo una comparación lexicográfica anidada. Si todos los elementos son iguales, se considera que las secuencias son iguales. Si una de las secuencias es igual a la otra truncada a partir de cierto elemento, la secuencia más corta de las dos es la menor. La ordenación lexicográfica para las cadenas utiliza el orden de los códigos ASCII de sus caracteres. He aquí ejemplos de comparaciones entre secuencias del mismo tipo:

(1, 2, 3)              < (1, 2, 4)
[1, 2, 3]              < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4)           < (1, 2, 4)
(1, 2)                 < (1, 2, -1)
(1, 2, 3)             == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Observa que es legal comparar objetos de tipos diferentes. El resultado es determinístico pero arbitrario: los tipos se ordenan por su nombre. De este modo, una lista siempre es menor que una cadena, una cadena siempre es menor que una tupla, etc. Los valores numéricos de tipos diferentes se comparan por su valor numérico, por lo que 0 es igual a 0.0, etc.