Saltar a contenido

Tuplas, diccionarios y otras colecciones

En este documento vamos a analizar otras estructuras de datos de Python que, si bien no son las más habituales, nos van a permitir organizar la información de cierta forma para poder acceder a ella.

1. Tuplas

Las tuplas son un tipo especial de datos en Python, que se parecen algo a las estructuras o structs de C o C#. Permiten definir un conjunto de datos heterogéneos, pero esos datos, una vez se definen, son inmutables, es decir, no podemos modificar su valor. Cada dato dentro de la tupla tiene un índice (empezando por cero), para poder acceder a cada dato.

Para definir una tupla, especificamos sus datos (entre paréntesis si queremos, aunque no es obligatorio), separados por comas. De hecho, la presencia de la coma es lo que marca la definición de una tupla:

nombres = ("John", "Helen", "Mary")
nombres2 = "Susan", "Adam", "Peter"

Como decimos, podemos especificar también diferentes datos en cada posición. Por ejemplo, para almacenar información personal de una persona:

datosPersonales = ("John Doe", 36, "611223344")

Después, podemos acceder a cada elemento con un índice entre corchetes, empezando por el 0:

print(datosPersonales[0])   # John Doe

Las tuplas ofrecen mucha flexibilidad a la hora de acceder a sus elementos, o asignarlos a variables separadas. Observemos este ejemplo:

datos = 20, "Nacho", 55.4
print(datos)                # (20, "Nacho", 55.4)
elem1, elem2, elem3 = datos
print(elem2)                # Nacho
elem2 = "May"
print(elem2)                # May
datos[1] = "May"            # Error: tupla inmutable

Como podemos ver, podemos sacar datos de una tupla de forma individual, y luego modificar esas variables. O bien tratar la tupla como un todo (variable datos anterior), en cuyo caso los valores almacenados son inmutables.

En cuanto a las operaciones habituales, con las tuplas se pueden hacer gran parte de las operaciones comunes en listas (siempre que no impliquen modificación de la tupla): buscar elementos en la tupla, obtener subtuplas (slicing), concatenar o repetir tuplas, calcular la longitud o número de elementos, obtener máximos, mínimos, conteo de ocurrencias, convertir una tupla a lista y recorrer sus elementos con un for. Aquí vemos un ejemplo de estas cosas:

datos = (1, 2, 3)
print(2 in datos)       # True
print(datos.index(3))   # 2
print(datos[1:3])       # (2, 3)
print(datos + (4, 5))   # (1, 2, 3, 4, 5)
print(datos * 2)        # (1, 2, 3, 1, 2, 3)
print(len(datos))       # 3
print(max(datos))       # 3
print(datos.count(2))   # 1
lista = list(datos)
for n in datos:
    print(n)            # Saca 1, luego 2 y luego 3

Ejercicio 1

Crea un programa llamado Tuplas.py donde le pidas al usuario que rellene su dirección postal, que estará formada por su nombre de calle (texto), número de puerta (entero) y número de piso (entero). Almacena los datos en una tupla y luego muestra por pantalla el resultado (campo a campo).

2. Diccionarios

Los diccionarios, también denominados mapas o tablas hash en muchos lenguajes de programación, permiten almacenar información en forma de pares clave-valor, donde cada valor almacenado en la colección tiene asociada una clave, y accedemos a dicho valor a través de su clave. Nos van a permitir almacenar información compleja y heterogénea, como por ejemplo todos los datos personales de un individuo (nombre, dirección, edad, etc) en una sola variable, y también podremos gestionar colecciones (listas) de diccionarios, aumentando así la complejidad de los datos almacenados.

Para definir un diccionario inicialmente vacío, se utiliza una pareja de llaves.

datos = {}

Si queremos asignar unos valores iniciales, podemos hacerlo indicando cada clave con su valor correspondiente. La clave y el valor se separan por dos puntos, y cada clave-valor se separa de las siguientes por una coma. Así podríamos almacenar los datos personales de alguien:

datos_personales = {
    'nombre': 'Nacho Iborra',
    'edad': 45,
    'telefono': '611223344'
}

Para acceder a cada valor del diccionario debemos hacerlo indicando su clave entre corchetes. Podemos hacerlo tanto para consultar un valor previamente asignado como para crear un nuevo valor con una nueva clave:

print(datos_personales['nombre']) # Nacho Iborra
datos_personales['edad'] = 46

Veamos otro ejemplo distinto de uso: imaginemos que estamos gestionando un diccionario con un catálogo de productos de una tienda. La clave para identificar cada producto será su código (alfanumérico, por ejemplo), y el valor asociado serán los datos del producto, que podemos representar como una tupla con su código, título y precio, por ejemplo. De este modo podríamos crear e inicializar el diccionario:

productos = {}
productos['111A'] = ('111A', 'Monitor LG 22 pulgadas', 99.95)
productos['222B'] = ('222B', 'Disco duro 512GB SSD', 109.45)
productos['333C'] = ('333C', 'Ratón bluetooth', 19.35)

En el caso de que queramos crear este catálogo de productos con unos valores iniciales en el programa, podemos hacerlo de este otro modo, similar a como hemos creado los datos personales de antes (aunque en este caso el valor asociado a cada clave es una tupla):

productos = {
    '111A': ('111A', 'Monitor LG 22 pulgadas', 99.95),
    '222B': ('222B', 'Disco duro 512GB SSD', 109.45)
}

Para acceder a un elemento en concreto, siempre tendrá que ser con su clave. Podemos utilizar el operador in para verificar previamente si existe la clave en el diccionario:

if '111A' in productos:
    print(productos['111A'][1]) # Monitor LG 22 pulgadas

También podemos recorrer todo el diccionario. En este caso podemos usar varias alternativas. Aquí mostramos algunas:

# Recorrer todas las claves y con ellas sacar los valores
for clave in productos:
    print(productos[clave][1])  # Títulos de los productos

# Recorrer todos los valores (ignoramos las claves)
for valor in productos.values():
    print(valor[1])             # Títulos de los productos

# Recorrer el diccionario sacando clave y valor
for clave, valor in productos.items():
    print(valor[1])             # Títulos de los productos

Existen otras operaciones habituales sobre diccionarios, como por ejemplo:

  • La operación pop elimina la clave que indiquemos del diccionario, junto con su valor asociado. También se puede usar del, aunque con pop se puede obtener el elemento que se ha borrado.
# Borramos el producto '222B' sin posibilidad de recuperarlo
del productos['222B']
# Borramos el producto '111A' y lo guardamos en una variable
prod_borrado = productos.pop('111A')
  • Las operaciones len y clear sirven, respectivamente, para obtener el número de elementos del diccionario, o para borrarlos todos.
print("El diccionario tiene", len(productos), "productos")
productos.clear()

Ejercicio 2

Crea un programa llamado DiccionarioTuplas.py donde le pidas al usuario que rellene direcciones de 4 usuarios, identificados por su clave que será el DNI. Así, para cada usuario rellenará dicho DNI, y luego los datos de la dirección como en el ejercicio anterior (nombre de calle, número de puerta y número de piso). Almacenará los datos en un diccionario (asociando cada DNI con su dirección) y luego le pedirá al usuario que escriba un DNI y mostrará los datos de su dirección, o el mensaje "El DNI no se encuentra almacenado" si no existe dicha clave.

Solución Ejercicio 2

Aquí puedes ver un vídeo con la solución paso a paso del ejercicio

2.1. Diccionarios y orden

El orden en que se añaden los elementos a un diccionario depende, en general, de una función llamada hashing que, dependiendo de la clave que se quiere añadir, le asigna una posición única en el diccionario. Dicho de otro modo, no podemos controlar el orden en que se añaden los elementos en un diccionario, aunque en algunos lenguajes, al recorrer las claves, suele coincidir con el orden en que se han añadido.

Sin embargo, aunque no podamos controlar el orden en que se añaden las cosas en el diccionario, sí es posible recorrerlas en un cierto orden, si obtenemos las claves y las ordenamos previamente (usando, por ejemplo, la instrucción sorted que se emplea para ordenar listas). Aquí vemos cómo podríamos recorrer nuestro catálogo de productos de un ejemplo anterior ordenando las claves alfabéticamente:

for clave in sorted(productos.keys()):
    print(productos[clave])

2.2. Listas y diccionarios

Como decíamos antes, los diccionarios pueden combinarse con otras estructuras de datos para gestionar información más compleja. Ya lo hemos visto antes en el diccionario de productos, donde el valor de cada clave era una tupla.

2.2.1. Listas de diccionarios

También podemos definir una lista donde cada elemento sea un diccionario que almacene datos de un elemento en concreto. Así, por ejemplo, podríamos crear una lista con datos personales de distintas personas:

personas = list()
personas.append({
    'nombre': 'Nacho Iborra',
    'edad': 45,
    'telefono': '611223344'
})
personas.append({
    'nombre': 'Juan Pérez',
    'edad': 63,
    'telefono': '699887766'
})

A la hora de acceder a los elementos de la lista, indicaremos el elemento (la persona) en cuestión con su posición numérica (empezando por 0) y luego indicaremos la clave del valor que queremos consultar. Por ejemplo, así mostramos la edad de la segunda persona de la lista:

print(personas[1]['edad'])  # 63

2.2.2. Diccionarios con listas

También podemos hacer la combinación inversa: que un valor de un diccionario sea una lista de elementos. Por ejemplo, así podemos definir los ingredientes de una pizza:

pizza = {
    nombre: 'Barbacoa',
    ingredientes: ['Tomate', 'Mozzarella', 'Bacon', 'Salsa barbacoa']
}

A la hora de acceder a alguno de los ingredientes de esta pizza (por ejemplo, el segundo), podríamos hacer algo así:

print pizza['ingredientes'][1]

2.2.3. Diccionarios con diccionarios

Las combinaciones que podemos hacer son muchas, como podemos ver. Una más: que el valor asociado a cada clave de un diccionario sea a su vez otro diccionario. Aquí vemos cómo la dirección postal de los contactos la representamos con otro diccionario incrustado:

datos_personales = {
    'nombre': 'Nacho Iborra',
    'edad': 45,
    'telefono': '611223344',
    'direccion': {
        'via': 'Calle',
        'nombre': 'Mayor',
        'numero': 20,
        'cp': 3001,
        'localidad': 'Alicante'
    }
}

Si queremos acceder, por ejemplo, al número de la calle, podríamos hacer algo así:

print(datos_personales['direccion']['numero'])

3. Conjuntos

Los conjuntos son un tipo de colección que no admite duplicados. Tienen muchas operaciones en común con listas o tuplas (len, in, clear, recorrido con for...). Se pueden crear con la instrucción set vacía, y también pasándole una lista (se eliminan automáticamente los duplicados). Las operaciones add y remove permiten añadir o quitar elementos al conjunto, respectivamente. También la instrucción discard elimina elementos y, a diferencia de remove, no produce una excepción si no encuentra el elemento a eliminar.

Conviene tener presente que en los conjuntos no existe un orden ni una secuencia numerada de elementos, como sí ocurre en las listas. Veamos un ejemplo:

lista = ["Uno", "Dos", "Dos", "Tres"]
conjunto_1 = set(lista)
print(conjunto_1)
# {"Dos", "Uno", "Tres"}
conjunto_1.add("Uno")
conjunto_1.add("Cuatro")
print(conjunto_1)
# {"Dos", "Uno", "Tres", "Cuatro"}
conjunto_1.remove("Tres")
print(conjunto)
# {"Dos", "Uno", "Cuatro"}

conjunto_2 = set()
conjunto_2.add(1)
conjunto_2.add(10)
conjunto_2.remove(5)    # Excepción (no existe el 5)
conjunto_2.discard(5)   # No tiene efecto (no existe el 5)
conjunto_2.clear()

Además, también tenemos disponibles algunas operaciones de conjuntos como la unión (un conjunto con todos los elementos de los dos conjuntos unidos), la intersección (conjunto con los elementos comunes de los dos conjuntos afectados) o la diferencia (conjunto con los elementos del primer conjunto que no están en el segundo). También podemos determinar si un conjunto es subconjunto de otro (es decir, tiene parte de los elementos del otro) o superconjunto (tiene los elementos del otro y algunos más)

c1 = {1, 2, 3, 4}
c2 = {2, 4, 6, 8}
c3 = {2, 3}

union = c1.union(c2)           # {1, 2, 3, 4, 6, 8}
union2 = c1 | c2               # Equivale a lo anterior
inter = c1.intersection(c2)    # {2, 4}
inter2 = c1 & c2               # Equivale a lo anterior
difer = c1.difference(c2)      # {1, 3}
difer2 = c1 - c2               # Equivale a lo anterior

print(c3.issubset(c1))         # True
print(c1.issuperset(c3))       # True