Track
Guía de los Hashmaps de Python
Cuando los profesionales de los datos hablan hoy de almacenamiento de datos, la mayoría de las veces se refieren al lugar donde se almacenan los datos, ya sean archivos locales, bases de datos SQL o NoSQL, o la nube. Sin embargo, otro aspecto importante relacionado con el almacenamiento de datos es cómo se almacenan.
El cómo del almacenamiento de datos suele tener lugar a un nivel inferior, en el núcleo mismo de los lenguajes de programación. Tiene que ver con el diseño de las herramientas que utilizan los profesionales de los datos, más que con cómo utilizar estas herramientas. Sin embargo, saber cómo se almacenan los datos es fundamental para que los profesionales de los datos comprendan los mecanismos subyacentes que hacen posible su trabajo. Es más, este conocimiento puede ayudarles a tomar mejores decisiones para mejorar el rendimiento informático.
En este artículo hablaremos de los hashmaps. Un hashmap es una estructura de datos que aprovecha las técnicas de hashing para almacenar datos de forma asociativa. Los mapas hash son estructuras de datos optimizadas que permiten realizar operaciones de datos más rápidas, como insertar, eliminar y buscar.
Muchos lenguajes de programación modernos, como Python, Java y C++, admiten hashmaps. En Python, los hashmaps se implementan mediante diccionarios, una estructura de datos muy utilizada y que probablemente conocerás. En las siguientes secciones, trataremos los conceptos básicos de los diccionarios, cómo funcionan y cómo implementarlos utilizando distintos paquetes de Python.
¿Qué es un Mapa Hash?
Para definir un hashmap, primero tenemos que entender qué es el hashing. El hashing es el proceso de transformar una clave dada o una cadena de caracteres en otro valor. El resultado es normalmente un valor más corto, de longitud fija, que hace que sea computacionalmente más fácil trabajar con él que con la clave original.
Los hashmaps, también conocidos como hashtable, representan una de las implementaciones más comunes del hashing. Hashmaps almacena pares clave-valor (por ejemplo, ID de empleado y nombre de empleado) en una lista accesible a través de su índice.
La idea de los hashmaps es distribuir las entradas (pares clave/valor) en una matriz de cubos. Dada una clave, una función hash calculará un índice distinto que sugiera dónde se puede encontrar la entrada. El uso de un índice en lugar de la clave original hace que los hashmaps sean especialmente adecuados para múltiples operaciones con datos, como la inserción, eliminación y búsqueda de datos.
Cómo funciona un mapa hash.
Para calcular el valor hash, o simplemente hash, una función hash genera nuevos valores según un algoritmo matemático de hash. Como los pares clave-valor son, en teoría, ilimitados, la función hashing mapeará las claves basándose en un tamaño de tabla determinado.
Hay múltiples funciones hash disponibles, cada una de ellas con sus pros y sus contras. El objetivo principal de una función hash es devolver siempre el mismo valor para la misma entrada.
Las más comunes son las siguientes:
- Método de división. Es la forma más sencilla y rápida de calcular valores hash. Esto se hace dividiendo la clave por el tamaño de la tabla y utilizando el resto como hash.
- Método del cuadrado medio. Encontrará el cuadrado de la clave dada, luego tomará los dígitos centrales y utilizará esos dígitos como índice del elemento.
- Método de multiplicación. Establece el índice hash a partir de la parte fraccionaria de multiplicar la clave por un número real grande.
- Método de plegado. Primero se divide la llave en trozos del mismo tamaño, se suma el resultado y se divide por el tamaño de la mesa. El hash es el recordatorio.
Hashmap en Python
Python implementa los hashmaps mediante el tipo de datos diccionario incorporado. Al igual que los hashmaps, los diccionarios almacenan datos en pares {clave:valor}. Una vez que hayas creado el diccionario (véase la sección siguiente), Python aplicará una función hash muy práctica para calcular el hash de cada clave.
Los diccionarios de Python tienen las siguientes características:
- Los diccionarios son mutables. Esto significa que podemos cambiar, añadir o eliminar elementos después de haber creado el diccionario.
- Los elementos están ordenados. En Python 3.6 y anteriores, los diccionarios eran desordenados, es decir, los elementos no tenían un orden definido. Sin embargo, tras la publicación de Python 3.7, los diccionarios pasaron a conservar el orden. Ahora, cuando crees un diccionario Python, las claves seguirán el orden indicado en el código fuente. Para saber más sobre las razones de este cambio, lee esta nota de Raymond Hettinger, uno de los principales desarrolladores de Python.
- Las claves son inmutables. Eso significa que las claves son siempre tipos de datos que no se pueden modificar. En otras palabras, los diccionarios sólo permitirán tipos de datos que sean hashables, como cadenas, números y tuplas. Por el contrario, las claves nunca pueden ser un tipo de dato mutable, como una lista.
- Las llaves son únicas. Las claves son únicas dentro de un diccionario y no pueden duplicarse dentro de él. Si se utiliza más de una vez, las entradas posteriores sobrescribirán el valor anterior.
Así que, si alguna vez te has preguntado por las diferencias entre hashmaps y diccionarios, la respuesta es sencilla. Un diccionario no es más que la implementación nativa de Python de los hashmaps. Mientras que un hashmap es una estructura de datos que puede crearse utilizando múltiples técnicas de hashing, un diccionario es un hashmap particular, basado en Python, cuyo diseño y comportamiento se especifican en la clase dict de Python.
Cómo utilizar los diccionarios de Python
Veamos algunas de las operaciones más habituales del diccionario. Para saber más sobre cómo utilizar los diccionarios, consulta nuestro Tutorial sobre diccionarios en Python.
Crear un diccionario
Crear diccionarios en Python es bastante sencillo. Sólo tienes que utilizar llaves e introducir los pares clave-valor separados por comas. También puedes utilizar la función incorporada dict(). Vamos a crear un diccionario que relacione las capitales con los países:
# Create dictionary
dictionary_capitals = {'Madrid": 'Spain", 'Lisboa': 'Portugal', 'London': 'United Kingdom'}
Para imprimir el contenido del diccionario:
print(dictionary_capitals)
{'Madrid': 'Spain', 'Lisboa': 'Portugal', 'London': 'United Kingdom'}
Es importante recordar que una clave tiene que ser única en un diccionario; no se permiten duplicados. Sin embargo, en caso de claves duplicadas, en lugar de dar un error, Python tomará como válida la última instancia de la clave y simplemente ignorará el primer par clave-valor. Compruébalo tú mismo:
dictionary_capitals = {'Madrid': 'China', 'Lisboa': 'Portugal',
'London': 'United Kingdom','Madrid':'Spain'}
print(dictionary_capitals)
{'Madrid': 'Spain', 'Lisboa': 'Portugal', 'London': 'United Kingdom'}
Buscar en un diccionario
Para buscar información en nuestro diccionario, tenemos que especificar la clave entre paréntesis, y Python nos devolverá el valor asociado, como se indica a continuación:
# Search for data
dictionary_capitals['Madrid']
'Spain'
Si intentas acceder a una clave que no está presente en el diccionario, Python lanzará un error. Para evitarlo, puedes acceder alternativamente a las llaves mediante el método .get()
. En caso de clave inexistente, devolverá simplemente un valor Ninguno:
print(dictionary_capitals.get('Prague'))
None
Añadir y eliminar valores en un diccionario
Añadamos un nuevo par capital-país:
# Create a new key-value pair
dictionary_capitals['Berlin'] = 'Italy'
La misma sintaxis puede utilizarse para actualizar el valor asociado a una clave. Fijemos el valor asociado a Berlín:
# Update the value of a key
dictionary_capitals['Berlin'] = 'Germany'
Ahora vamos a eliminar una de las parejas de nuestro diccionario
# Delete key-value pair
del dictionary_capitals['Lisboa']
print(dictionary_capitals)
{'Madrid': 'Spain', 'London': 'United Kingdom', 'Berlin': 'Germany'}
O, si quisieras borrar todos los pares clave-valor del diccionario, podrías utilizar el método .clear()
:
dictionary_capitals.clear()
Recorrer diccionarios
Si quieres recuperar todos los pares clave-valor, utiliza el método .items()
, y Python recuperará una lista iterable de tuplas:
dictionary_capitals.items()
dict_items([('Madrid', 'Spain'), ('London', 'United Kingdom'), ('Berlin', 'Germany')])
# Iterate over key-value pairs
for key, value in dictionary_capitals.items():
print('the capital of {} is {}'.format(value, key))
the capital of Spain is Madrid
the capital of United Kingdom is London
the capital of Germany is Berlin
Igualmente, si quieres recuperar una lista iterable con las claves y los valores, puedes utilizar los métodos .keys()
y .values()
, respectivamente:
dictionary_capitals.keys()
dict_keys(['Madrid', 'London', 'Berlin'])
for key in dictionary_capitals.keys():
print(key.upper())
MADRID
LONDON
BERLIN
dictionary_capitals.values()
dict_values(['Spain', 'United Kingdom', 'Germany'])
for value in dictionary_capitals.values():
print(value.upper())
SPAIN
UNITED KINGDOM
GERMANY
Aplicaciones reales de los Hashmaps
Los Hashmaps son potentes estructuras de datos que se utilizan prácticamente en todo el mundo digital. A continuación encontrarás una lista de aplicaciones reales de los hashmaps:
- Indexación de bases de datos. Los mapas hash se utilizan a menudo para indexar y buscar volúmenes masivos de datos. Los navegadores web comunes utilizan hashmaps para almacenar las páginas web indexadas.
- Gestión de la caché. Los sistemas operativos modernos utilizan hashmaps para organizar la memoria caché y permitir un acceso rápido a la información de uso frecuente.
- Criptografía. Los mapas hash desempeñan un papel fundamental en el campo de la criptografía. Los algoritmos criptográficos aprovechan los hashmaps para permitir la integridad de los datos, su validación y las transacciones seguras a través de las redes.
- Blockchain. Los Hashmaps son el núcleo de la cadena de bloques. Cada vez que se produce una transacción en la red, los datos de esa transacción se toman como entrada para la función hash, que luego produce una salida única. Cada bloque de la cadena de bloques lleva el hash del bloque anterior, formando una cadena de bloques.
Mejores prácticas y errores comunes de Hashmap
Los mapas hash son estructuras de datos increíblemente versátiles y eficaces. Sin embargo, también conllevan problemas y limitaciones. Para abordar los retos habituales asociados a los hashmaps, es importante tener en cuenta algunas consideraciones y buenas prácticas.
Las claves deben ser inmutables
Esto tiene sentido: si el contenido de la clave cambia, la función hash devolverá un hash diferente, por lo que Python no podrá encontrar el valor asociado a la clave.
Abordar las colisiones del hashmap
El hashing sólo funciona si cada elemento se asigna a una ubicación única en la tabla hash. Pero a veces, las funciones hash pueden devolver la misma salida para entradas diferentes. Por ejemplo, si utilizas una función hash de división, distintos enteros pueden tener la misma función hash (pueden devolver el mismo resto al aplicar la división del módulo), creando así un problema llamado colisión. Las colisiones deben resolverse, y existen varias técnicas. Por suerte, en el caso de los diccionarios, Python gestiona las posibles colisiones bajo el capó.
Comprender el factor de carga
El factor de carga se define como la relación entre el número de elementos de la tabla y el número total de cubos. Es una medida para estimar lo bien distribuidos que están los datos. Como regla general, cuanto más uniformemente se distribuyan los datos, menor será la probabilidad de colisiones. De nuevo, en el caso de los diccionarios, Python adapta automáticamente el tamaño de la tabla en caso de nuevas inserciones o supresiones de pares clave-valor.
Sé consciente del rendimiento
Una buena función hash minimizaría el número de colisiones, sería fácil de calcular y distribuiría uniformemente los elementos de la tabla hash. Esto podría hacerse aumentando el tamaño de la tabla o la complejidad de la función hash. Aunque esto es práctico para un número pequeño de elementos, no es factible cuando el número de elementos posibles es grande, ya que daría lugar a hashmaps que consumirían mucha memoria y serían menos eficientes.
¿Los Diccionarios son lo que necesitas?
Los diccionarios están muy bien, pero otras estructuras de datos pueden ser más adecuadas para tus datos y necesidades concretas. Al final, los diccionarios no admiten operaciones habituales, como la indexación, el troceado y la concatenación, lo que los hace menos flexibles y más difíciles de trabajar en determinados escenarios.
Implementaciones alternativas de Hashmap en Python
Como ya se ha dicho, Python implementa hashmaps mediante diccionarios incorporados. Sin embargo, es importante tener en cuenta que existen otras herramientas nativas de Python, así como bibliotecas de terceros, para aprovechar la potencia de los hashmaps.
Veamos algunos de los ejemplos más populares.
Defaultdict
Cada vez que intentes acceder a una clave que no esté presente en tu diccionario, Python devolverá un KeyError. Una forma de evitarlo es buscar información utilizando el método .get()
. Sin embargo, una forma optimizada de hacerlo es utilizando un Defaultdict, disponible en las colecciones de módulos. Defaultdicts y diccionarios son casi lo mismo. La única diferencia es que defaultdict nunca genera un error porque proporciona un valor por defecto para claves inexistentes.
from collections import defaultdict
# Defining the dict
capitals = defaultdict(lambda: "The key doesn't exist")
capitals['Madrid'] = 'Spain'
capitals['Lisboa'] = 'Portugal'
print(capitals['Madrid'])
print(capitals['Lisboa'])
print(capitals['Ankara'])
Spain
Portugal
The key doesn't exist
Contador
Contador es una subclase de diccionario de Python diseñada específicamente para contar objetos con hash. Es un diccionario en el que los elementos se almacenan como claves y sus recuentos se almacenan como valores.
Hay varias formas de inicializar Contador:
- Por una secuencia de elementos.
- Por claves y recuentos en un diccionario.
- Utilizando la correspondencia nombre:valor.
from collections import Counter
# a new counter from an iterable
c1 = Counter(['aaa','bbb','aaa','ccc','ccc','aaa'])
# a new counter from a mapping
c2 = Counter({'red': 4, 'blue': 2})
# a new counter from keyword args
c3 = Counter(cats=4, dogs=8)
# print results
print(c1)
print(c2)
print(c3)
Counter({'aaa': 3, 'ccc': 2, 'bbb': 1})
Counter({'red': 4, 'blue': 2})
Counter({'dogs': 8, 'cats': 4})
La clase Contador incluye una serie de métodos prácticos para realizar cálculos comunes.
print('keys of the counter: ', c3.keys())
print('values of the counter: ',c3.values())
print('list with all elements: ', list(c3.elements()))
print('number of elements: ', c3.total()) # number elements
print('2 most common occurrences: ', c3.most_common(2)) # 2 most common occurrences
dict_keys(['cats', 'dogs'])
dict_values([4, 8])
['cats', 'cats', 'cats', 'cats', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs', 'dogs']
12
[('dogs', 8), ('cats', 4)]
Métodos hash de Scikit-learn
Scikit-learn, también conocido como sklearn, es una robusta biblioteca de aprendizaje automático en Python de código abierto. Se creó para ayudar a simplificar el proceso de implementación del aprendizaje automático y los modelos estadísticos en Python.
Sklearn viene con varios métodos hash que pueden ser muy útiles para los procesos de ingeniería de características.
Uno de los más comunes es el método CountVectorizer
. Se utiliza para transformar un texto dado en un vector basado en la frecuencia de cada palabra que aparece en todo el texto. CountVectorized es especialmente útil en contextos de análisis de texto.
from sklearn.feature_extraction.text import CountVectorizer
documents = ["Welcome to this new DataCamp Python course",
"Welcome to this new DataCamp R skill track",
"Welcome to this new DataCamp Data Analyst career track"]
# Create a Vectorizer Object
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(documents)
# print unique values
print('unique words: ', vectorizer.get_feature_names_out())
#print sparse matrix with word frequency
pd.DataFrame(X.toarray(), columns = vectorizer.get_feature_names_out())
unique words: ['analyst' 'career' 'course' 'data' 'datacamp' 'new' 'python' 'skill' 'this' 'to' 'track' 'welcome']
Hay otros métodos hash en sklearn, como FeatureHasher y DictVectorizer. Nuestro Curso de Presupuestos Escolares con Aprendizaje Automático en Python es un gran ejemplo en el que puedes aprender cómo funcionan en la práctica.
Conclusión
Enhorabuena por terminar este tutorial sobre hashmaps. Esperamos que ahora comprendas mejor los hashmaps y los diccionarios de Python. Si quieres aprender más sobre los diccionarios y cómo utilizarlos en situaciones reales, te recomendamos encarecidamente que leas nuestro Tutorial dedicado a los diccionarios de Python, así como nuestro Tutorial de comprensión de diccionarios de Python.
Por último, si te estás iniciando en Python y quieres aprender más, sigue el curso Introducción a la Ciencia de Datos en Python de DataCamp y consulta nuestro Tutorial de Python para Principiantes.
Soy analista de datos autónomo y colaboro con empresas y organizaciones de todo el mundo en proyectos de ciencia de datos. También soy instructor de ciencia de datos con más de 2 años de experiencia. Escribo regularmente artículos relacionados con la ciencia de datos en inglés y español, algunos de los cuales se han publicado en sitios web consolidados como DataCamp, Towards Data Science y Analytics Vidhya Como científico de datos con formación en ciencias políticas y derecho, mi objetivo es trabajar en la interacción de las políticas públicas, el derecho y la tecnología, aprovechando el poder de las ideas para promover soluciones y narrativas innovadoras que puedan ayudarnos a abordar retos urgentes, como la crisis climática. Me considero autodidacta, aprendiz constante y firme partidaria de la multidisciplinariedad. Nunca es demasiado tarde para aprender cosas nuevas.
¡Comienza hoy tu viaje en Python!
Course
Python intermedio
Course