Hashing, hash tables (tabla hash) y diccionarios en Python

En esta reunión veremos brevemente una nueva estructura de datos en Python llamada diccionarios y para entender mejor su funcionalidad hablaremos un poco de unos conceptos que en ciencias de la computación se llaman hash tables

Empezaremos con hashing y hash tables.

Imagina que estás en mi cuarto y que yo soy una persona en extremo desordenada.Mi ropa está tirada en el piso, sobre las sillas, en el baño; hay platos con comida sobre la cama, mesa, fregadero; y los zapatos están debajo de la mesa, en el clóset, bajo la cama... En fin, todo una zona de desastre!! Ahora imagina que te pido que me ayudes a encontrar un anillo que quiero mostrarte. ¿Cómo lo harías? ¿Cuánto tiempo tardarías?

Pues lo que la mayoría de la gente haría es empezar a hacer una búsqueda de uno pasando por diferentes lugares. En el mejor de los casos, asumamos que tuviste suerte y lo encontraste en el primer intento. En este caso no tuviste que gastar mucha energía ni tiempo en encontrarlo. Ahora veamos la contraparte, en el peor de los casos, lo vas a encontrar después de haber puesto de cabeza mi cuarto y haber buscado en cada rincón, debajo de cada objeto. Esta vez tuviste que invertir mucho tiempo y energía en encontrar el anillo que yo quería mostrarte (y al final ni siquiera te gustó u.u)

Pensemos un momento en qué estrategia usaríamos para resolver este problema...

Tiempo para la reflexión

3...

2...

1...

Creo que todos estaríamos de acuerdo con que la solución a mi problema sería ordenar mi cuarto, pero reflexionemos acerca de lo que hacemos cuando limpiamos nuestro cuarto.

Básicamente lo que haga cuando ordeno mi cuarto es algo así como crear contenedores que tengan una categoría especial de objetos, por ejemplo mi clóset es un contenedor para ropa, zapatos, calcetas, etc., mi librero es un contenedor para libros, libretas usadas, documentos, películas, videojuegos, etc., y mi tocador es un contenedor para espejo, crema, anillos, relojes, etc. A su vez, estos contenedores se pueden subdividir en nuevos contenedores, en el clóset tengo cajones para calcetas, pijamas, un espacio para zapatos y otro para colgar vestidos y chamarras.

Cuando tengo todo arreglado dentro de los contenedores es más fácil para mi buscar un objeto en específico que si tuviera que buscarlo en un mar de desorden. Así si quiero mi anillo yo sé de antemano que tengo que buscar primero en el contenedor "tocador", luego dentro de eso hay un cajón para "accesorios" y dentro de ese cajón debería de ser capaz de encontrar mi anillo.

Toda esta historia no es para presumir mi naturaleza desordenada, sino para explicar el concepto de hash tables.

Las hash tables (o tablas hash) es una forma de arreglar datos de manera tal que tengamos pares de llaves-valores. Para hacerlo más claro las llaves en el ejemplo anterior serían los cajones y los valores serían las calcetas.

La función principal de las tablas hash, como probablemente lo habrás inferido, es acceder de forma más eficiente a los valores. Así de sencillo...

Para poder construir las tablas necesitamos de una función hash (hashing). Lo que esta función debe de ser capaz de hacer el tomar un par de elementos (llave, valor) y asignarles un espacio definido en la memoria...

¿Qué tiene que ver todo esto con Python?

Hay una implementación de tablas hash en Python y es una estructura que se llama diccionario. Justamente es lo que discutiremos hoy.

Los diccionarios en python son una forma de estructurar datos en los cuales vamos a encontrar elementos(valores) de acuerdo a un índice(llaves). Las llaves deben ser objetos inmutables que sean únicos, es decir que no existan dos llaves con el mismo nombre y los valores pueden ser de cualquier tipo.

Estructura de los diccionarios en Python

Para que a python le quede claro que una serie de elementos son un diccionario, lo primero que tenemos que hacer es inicializarlos con las llaves {}. Una vez hecho esto lo que tenemos que hacer es ir escribiendo los pares llave-valor con dos puntos así 'llave':'valor', si queremos que nuestro diccionario tenga más de una entrada, lo que tenemos que hacer es separar con una coma cada para de llave-valor. Ejemplo de un diccionario de pares correspondientes:

diccionario = {'Steve Jobs': 'Apple', 'Bill Gates': 'Microsoft', 'Mark Zuckerberg': 'Facebook'}

Ejercicio 1

Crea un diccionario con los nombres de las personas en el grupo y la edad de cada uno de ellos


In [ ]:

Ejercicio 2

¿Qué pasa si hay dos personas con el mismo nombre?


In [ ]:

Ejercicio 3

¿Cómo te imaginas que se declara un diccionario vacío?


In [ ]:

Manipulación de diccionarios

Como mencionamos anteriormente las llaves en los diccionarios van a ser los índices sobre los cuales vamos a poder acceder a sus valores, por ejemplo:

diccionario[llave]

Va a darte como resultado el valor de esa llave. Así como podemos acceder a los valores, de la misma forma podemos crear nuevas entradas en los diccionarios. Por ejemplo:

diccionario[llave_nueva] = valor_nuevo

va a agregar un nuevo par de llave-valor.

Las iteraciones sobre los diccionarios son iguales a las iteraciones sobre otros objetos.

Hay algunas funciones y métodos ya implementados en diccionarios que te pueden servir para operar sobre estos. A continuación te presento una lista de cosas que puedes hacer:

  1. len(diccionario): Esto te permite saber cuántas entradas tiene tu diccionario.

  2. del diccionario[llave]: Con esto puedes borrar un elemento específico de un diccionario.

  3. diccionario.pop[llave]:Con esto puedes hacer algo parecido pero además de quitar ese elemento del diccionario, puedes guardar el valor de esa llave en una variable.

  4. diccionario.popitem(): Con este método puedes sacar un par de llave-valor de un diccionario y esto lo hace en forma de tuple(otro tipo de estructura de datos que veremos más adelante). El par llave-valor que se obtiene es arbitrario.

  5. llave in diccionario: Esto te permite saber si una llave en específico existe en un diccionario. Es exactamente como lo hacíamos en listas y strings... Como resultado obtienes una expresión booleana que te dice True si el elemento existe y False en caso de que no exista. Funciona igual con not in.

  6. diccionario.clear(): Este método te permite vaciar el contenido de un diccionario.

  7. diccionario.update(diccionario2): Te permite unir dos diccionarios, y si tienen llaves iguales, lo que hacen es tomar los valores como un set(otro tipo de estructura de datos).

  8. diccionario.copy(): Esto te resulta en una copia superficial del diccionario.

  9. diccionario.keys(): Retorna una lista de las llaves del diccionario.

  10. diccionario.values(): Retorna una lista de los valores del diccionario.

Recuerda que si quieres saber que otros métodos y funciones hay simpre puedes usar el "?" o presionar tab después de escribir el nombre del diccionario seguido de un punto.

Ejercicio 4

Crea una función que te de como resultado un diccionario con las cuentas de una palabra. Por ejemplo:

contador('parangaricutirimicuaro')

debe darte como resultado

{'a': 4, 'c': 2, 'g': 1, 'i': 4, 'm': 1, 'n': 1, 'o': 1, 'p': 1, 'r': 4, 't': 1, 'u': 2}


In [10]:
def contador(palabra):
    letras = {}
    for letra in palabra:
        if letra in letras:
            letras[letra] = letras[letra] + 1
        else:
            letras[letra] = 1
    return letras

In [11]:
contador('parangaricutirimicuaro')


Out[11]:
{'a': 4,
 'c': 2,
 'g': 1,
 'i': 4,
 'm': 1,
 'n': 1,
 'o': 1,
 'p': 1,
 'r': 4,
 't': 1,
 'u': 2}

Ejercicio 5

Ahora haremos una función que en lugar de contar letras en palabras, cuente palabras en textos. Para esto vamos a usar un fragmento del poema de Sor Juana Inés de la Cruz llamado "Hombres necios"

Hombres necios que acusáis a la mujer sin razón, sin ver que sois la ocasión de lo mismo que culpáis:

si con ansia sin igual solicitáis su desdén, ¿por qué queréis que obren bien si las incitáis al mal?

Combatís su resistencia y luego, con gravedad, decís que fue liviandad lo que hizo la diligencia.

Como pista les dejo la función string.split()


In [1]:
#Código que ya preparado para tener en un string el fragmento de sor Juana

sor_juana = open('sor_juana.txt', 'r').read()

In [2]:
sor_juana


Out[2]:
'Hombres necios que acusáis a la mujer sin razón, sin ver que sois la ocasión de lo mismo que culpáis: si con ansia sin igual solicitáis su desdén, ¿por qué queréis que obren bien si las incitáis al mal? Combatís su resistencia y luego, con gravedad, decís que fue liviandad lo que hizo la diligencia. \n'

In [2]:
split = sor_juana.split()

In [7]:
def word_counter(text):
    dictionary={}
    for element in split:
        if element in dictionary:
            dictionary[element] = dictionary[element] + 1
        else:
            dictionary[element] = 1
    return dictionary

In [8]:
word_counter(split)


Out[8]:
{'Combatís': 1,
 'Hombres': 1,
 'a': 1,
 'acusáis': 1,
 'al': 1,
 'ansia': 1,
 'bien': 1,
 'con': 2,
 'culpáis:': 1,
 'de': 1,
 'decís': 1,
 'desdén,': 1,
 'diligencia.': 1,
 'fue': 1,
 'gravedad,': 1,
 'hizo': 1,
 'igual': 1,
 'incitáis': 1,
 'la': 3,
 'las': 1,
 'liviandad': 1,
 'lo': 2,
 'luego,': 1,
 'mal?': 1,
 'mismo': 1,
 'mujer': 1,
 'necios': 1,
 'obren': 1,
 'ocasión': 1,
 'que': 6,
 'queréis': 1,
 'qué': 1,
 'razón,': 1,
 'resistencia': 1,
 'si': 2,
 'sin': 3,
 'sois': 1,
 'solicitáis': 1,
 'su': 2,
 'ver': 1,
 'y': 1,
 '¿por': 1}

Bonus

Las que quieran pueden hacer su código de tal modo que los acentos y las mayúsculas no hagan que las palabras sean diferentes y se cuenten en dos categorías. Ejemplo

que es igual a qué por lo tanto deberían contarse como 2 y no como uno cada uno en su categoría.

Solución propuesta por Erika Peláez

Aquí hago una aclaración porque creo que es didáctico mostrar que hay muchas formas de atacar los problemas en Python. La que muestro a continuación es la más clara en cuanto a sintaxis en el nivel que estamos viendo. Sin embargo hay que notar que hay soluciones que ocupan menos tiempo de cómputo y por lo tanto serían las más óptimas. En esta página se encuentran las descripciones de algunas de las funciones que utilicé. En esta otra hay unos ejemplos con otros códigos que hacen algo parecido pero vienen los tiempos que toman cada una de las estrategias.


In [ ]:


In [8]:
def word_counter2(text):
    map_str = str.maketrans({'á':'a','é':'e','í':'i','ó':'o','ú':'u','¿':'','?':'','.':''})
    dictionary={}
    for element in text:
        llave = element.translate(map_str).capitalize()
        if llave in dictionary:
            dictionary[llave] = dictionary[llave] + 1
        else:
            dictionary[llave] = 1
    return dictionary

In [9]:
word_counter2(split)


Out[9]:
{'A': 1,
 'Acusais': 1,
 'Al': 1,
 'Ansia': 1,
 'Bien': 1,
 'Combatis': 1,
 'Con': 2,
 'Culpais:': 1,
 'De': 1,
 'Decis': 1,
 'Desden,': 1,
 'Diligencia': 1,
 'Fue': 1,
 'Gravedad,': 1,
 'Hizo': 1,
 'Hombres': 1,
 'Igual': 1,
 'Incitais': 1,
 'La': 3,
 'Las': 1,
 'Liviandad': 1,
 'Lo': 2,
 'Luego,': 1,
 'Mal': 1,
 'Mismo': 1,
 'Mujer': 1,
 'Necios': 1,
 'Obren': 1,
 'Ocasion': 1,
 'Por': 1,
 'Que': 7,
 'Quereis': 1,
 'Razon,': 1,
 'Resistencia': 1,
 'Si': 2,
 'Sin': 3,
 'Sois': 1,
 'Solicitais': 1,
 'Su': 2,
 'Ver': 1,
 'Y': 1}

In [ ]:

Súper Bonus

Escribe una función que te permita encriptar textos. La encriptación sería recorrer el alfabeto en un número determinado de posiciones. Por ejemplo. Digamos que tengo un string que tiene el siguiente texto.

"La fiesta es a las 5"

Lo que haré es decir que voy a mover el abecedario en 2 letras. El abecedario quedaría así:

a = c, b = d, c = e, d = f, e = g, f = h, g = i, h = j, i = k, j = l, k = m, l = n, m = o, n = p, o = q, p = r, q = s, r = t, s = u, t = v, u = w, v = x, w = y, x = z, y = a, z = b.

Por lo tanto el texto quedaría encriptado como:

"Nc hkguvc gu c ncu 5"


In [16]:
ord('a')+3


Out[16]:
100

In [17]:
chr(100)


Out[17]:
'd'

In [27]:
def codificador(palabra, shift):

In [16]:
import string

In [23]:
dictionary=dict(zip(list(string.ascii_letters), [ord(letra) for letra in string.ascii_letters]))

In [25]:
[ord(letra)+3 for letra in string.ascii_letters]


Out[25]:
[100,
 101,
 102,
 103,
 104,
 105,
 106,
 107,
 108,
 109,
 110,
 111,
 112,
 113,
 114,
 115,
 116,
 117,
 118,
 119,
 120,
 121,
 122,
 123,
 124,
 125,
 68,
 69,
 70,
 71,
 72,
 73,
 74,
 75,
 76,
 77,
 78,
 79,
 80,
 81,
 82,
 83,
 84,
 85,
 86,
 87,
 88,
 89,
 90,
 91,
 92,
 93]

In [24]:
dictionary


Out[24]:
{'A': 65,
 'B': 66,
 'C': 67,
 'D': 68,
 'E': 69,
 'F': 70,
 'G': 71,
 'H': 72,
 'I': 73,
 'J': 74,
 'K': 75,
 'L': 76,
 'M': 77,
 'N': 78,
 'O': 79,
 'P': 80,
 'Q': 81,
 'R': 82,
 'S': 83,
 'T': 84,
 'U': 85,
 'V': 86,
 'W': 87,
 'X': 88,
 'Y': 89,
 'Z': 90,
 'a': 97,
 'b': 98,
 'c': 99,
 'd': 100,
 'e': 101,
 'f': 102,
 'g': 103,
 'h': 104,
 'i': 105,
 'j': 106,
 'k': 107,
 'l': 108,
 'm': 109,
 'n': 110,
 'o': 111,
 'p': 112,
 'q': 113,
 'r': 114,
 's': 115,
 't': 116,
 'u': 117,
 'v': 118,
 'w': 119,
 'x': 120,
 'y': 121,
 'z': 122}

In [ ]:


In [ ]:


In [ ]: