Tabla Hash
¿Qué representa este TAD?
Una tabla hash asocia cada clave con un valor y calcula dónde guardarlo. Si dos claves caen en el mismo bucket —casilla de la tabla—, se conservan en una cadena.
Prueba este ejemplo
Crea capacidad 5. Inserta (clave 1, valor 10), luego (1, 20) y (6, 30). Buscar clave 1 devuelve 20. Hay dos entradas en el bucket 1: actualizar y colisionar no son lo mismo.
Qué observar · tres pasos
- Distingue la clave usada para buscar del valor almacenado.
- Observa el índice calculado y la cadena de ese bucket.
- Comprueba que actualizar una clave conserva la cantidad de entradas. La capacidad queda fija; no hay Agregar varios en este módulo.
Ir a la visualización e ingresar estos datos
Límites y datos anteriores
- El visualizador admite entre 1 y 128 buckets. Claves y valores son enteros de 32 bits.
- Las sesiones antiguas mayores quedan suspendidas, no vacías. Exporta historial y main.c antes de restablecerlas.
Capacidad del visualizador frente a capacidad del C
El límite de 128 pertenece a la aplicación. El TAD C recibe un int positivo: INT_MAX limita el tipo, pero no garantiza memoria disponible. El tamaño en bytes debe caber en size_t y la reserva puede fallar.
De la idea al resultado
- Prepara una operación y predice qué cambiará.
- Activa Paso a paso antes de ejecutar si quieres ver la traza; sin ese modo obtienes el resultado final.
- Relaciona el dibujo con la línea C resaltada: acaba de ejecutarse. Una condición puede no cambiar ningún nodo.
- Lee las pistas de Comprender y las pestañas de Resultados.
Paso anterior / Siguiente paso revisan la misma llamada. Pausar no deshace; cerrar Paso a paso muestra su final sin ejecutarla otra vez. Restablecer TAD (o Reiniciar, según el módulo) borra estructura e historial, no sólo el cursor.
Usa −, +, 100%, Encajar vista y Centrar inicio para acomodar el gráfico. El zoom y el desplazamiento se conservan durante los pasos; la memoria auxiliar es opcional.
Palabras básicas para leer el dibujo
- TAD
- Tipo abstracto de dato: los datos y las operaciones permitidas sobre ellos.
- Nodo
- Elemento que guarda un valor y enlaces a otros elementos.
- Puntero o enlace
- Referencia que permite llegar a otro nodo. NULL indica que no apunta a un nodo.
- Raíz, padre e hijo
- La raíz inicia el árbol; un padre se conecta con sus hijos.
- Tope, frente y atrás
- Tope (TOP) en Pila; frente (FRONT) y atrás (BACK) en Cola son referencias a sus extremos.
Primera práctica de 5 minutos · Copiar, ampliar y exportar · Resolver dudas y errores · Atajos y referencia
Profundizar: reglas, memoria y costo
Cómo razonar sobre el algoritmo
Explicar cómo el residuo C selecciona un bucket y cómo la cadena de punteros resuelve una colisión.
Calcular índice, normalizar negativos, recorrer actual/anterior y enlazar o liberar solo en la rama ejecutada.
Reglas que comprobar al terminar (invariantes)
- Cada clave está en el bucket normalizado por clave % capacidad.
- Las claves son únicas.
- Al terminar, cantidad coincide con los nodos alcanzables; entre publicar cabecera y cantidad++ se muestra el incremento pendiente.
- Toda cadena termina en NULL y no tiene ciclos.
Memoria y propiedad de los datos
Insertar una clave nueva reserva un THNodo con malloc; actualizar no reserva. Recrear libera primero la instancia previa: th_inicializar por sí solo no la libera. Los campos del nodo nuevo se inicializan antes de publicar la cabecera; malloc NULL retorna false. Eliminar, vaciar y destruir liberan memoria con free.
Costo y tamaño de entrada
El costo describe cómo crece el trabajo al aumentar los datos, no cuántos segundos tardará la animación. n es el número de entradas y α es cantidad/capacidad.
- mejor: Θ(1) para una clave; listar, estadísticas y vaciar recorren buckets y cadenas.
- promedio: Θ(1 + α) con distribución aproximadamente uniforme para una clave; listar, estadísticas y vaciar recorren buckets y cadenas.
- peor: Θ(n) si todas las claves colisionan para una clave; listar, estadísticas y vaciar recorren buckets y cadenas.
Errores que conviene evitar
- Confundir el módulo de Python con el residuo negativo de C.
- Suponer que la tabla se redimensiona automáticamente.
- Olvidar actualizar anterior al eliminar un nodo intermedio.
- Usar un nodo después de free.
¿Dónde se utiliza?
- Índices por identificador
- tablas de símbolos
- cachés con clave entera
- conteo y asociación de datos
Glosario: palabras de esta guía
- Bucket
- Celda del arreglo que apunta a una cadena.
- Colisión
- Dos claves diferentes seleccionan el mismo bucket.
- Factor de carga α
- cantidad / capacidad.
- Encadenamiento separado
- Estrategia que conserva las colisiones en listas enlazadas.
- Residuo C
- Resultado de %, que puede ser negativo y se normaliza antes de indexar.
- NULL
- Puntero nulo que marca el final de una cadena.
- Capacidad fija
- Número de buckets que no cambia durante la vida de esta implementación.
Teclado
- Alt+→ avanza al siguiente paso de la traza preparada.
- Alt+←: paso anterior.
- Alt+Inicio: inicio.
- Alt+Fin: final.
- Alt+P: pausar.
Alcance de esta pantalla
- El TAD no expone técnicas alternativas de hashing (lineal/cuadrático/doble hash).
- La capacidad es fija: no hay resize ni rehash automático en el C interpretado.
Referencia de operaciones y datos de entrada
Operaciones de la aplicación y datos
- Limpiar tabla
clear - Sin datos adicionales.
- Verificar existencia de clave
contains - Clave (entero)
- Crear tabla
create_table - Capacidad (entero)
- Destruir tabla
destroy_table - Sin datos adicionales.
- Buscar clave
get - Clave (entero)
- Insertar/Actualizar clave-valor
insert - Clave (entero); Valor entero (entero)
- Listar ítems
items - Sin datos adicionales.
- Listar claves
keys - Sin datos adicionales.
- Eliminar clave
remove - Clave (entero)
- Tamaño/capacidad/factor
stats - Sin datos adicionales.
- Listar valores
values - Sin datos adicionales.
Los métodos C siguientes pueden incluir helpers, utilidades sin botón propio y llamadores equivalentes. Un bloque con varias funciones no cambia el nombre de la operación de la aplicación. Las vistas de memoria usan identidades simbólicas; tras free, sus registros históricos no son punteros utilizables.
Claves y valores son enteros int32. La aplicación acepta de 1 a 128 buckets. El TAD C recibe una capacidad int positiva, limitada por INT_MAX y la memoria disponible; permanece fija durante su vida; la carga se calcula como estadística, no configura un rehash. Vaciar conserva los buckets; destruir termina su vida. La aplicación diferencia esas operaciones.
Ejecutar operación crea una llamada; Anterior/Siguiente recorren la misma traza. Cerrar y reactivar Paso a paso no vuelven a ejecutar. Las consultas completadas pueden conservarse en el historial o journal aunque no muten la estructura; los rechazos no agregan llamadas exitosas. Restablecer TAD (o Reiniciar, según el módulo) borra la estructura y su historial.
Código fuente completo del TAD
Descarga el archivo C principal de Tabla hash y su encabezado para estudiarlo o compilarlo en tu equipo.
Referencia: estructura del TAD en C
typedef struct th_nodo {
int clave; /**< Clave entera del elemento. */
int valor; /**< Dato almacenado. */
struct th_nodo *siguiente; /**< Enlace al siguiente nodo del bucket. */
} THNodo;
typedef struct {
THNodo **buckets; /**< Arreglo de cabezas de cadenas de colisión. */
int capacidad; /**< Capacidad reservada de la estructura. */
int cantidad; /**< Número de elementos almacenados. */
} TablaHash;
typedef struct {
int capacidad; /**< Capacidad reservada de la estructura. */
int cantidad; /**< Número de elementos almacenados. */
int buckets_ocupados; /**< Número de buckets con al menos un nodo. */
int colisiones; /**< Elementos que exceden el primer nodo de cada bucket ocupado. */
float factor_carga; /**< Cociente float de metadata cantidad/capacidad en tabla utilizable; cero en salida vacia de error. */
} THEstadisticas;Código C y llamadores equivalentes
Crear tabla · código C
Inicializa los buckets con capacidad fija. Recrear en la aplicación destruye antes la tabla anterior; th_inicializar por sí solo no la libera.
/**
* @brief Inicializa la tabla hash.
* @param[out] tabla Puntero a la tabla hash.
* @param[in] capacidad Capacidad (número de buckets) de la tabla.
* @pre Estructura sin recursos propietarios pendientes; capacidad * sizeof(THNodo*) debe caber en size_t.
* @note NULL tabla o capacidad <= 0 retorna sin escribir: no inicializa una estructura indeterminada. No libera recursos previos: destruir antes de reinicializar.
* @note Con capacidad positiva fija cantidad=0; malloc fallido deja buckets=NULL y capacidad=0. Retorno void no comunica exito. No rehash.
*/
void th_inicializar(TablaHash *tabla, int capacidad) {
if (!tabla || capacidad <= 0) return;
tabla->capacidad = capacidad;
tabla->cantidad = 0;
tabla->buckets = (THNodo **)malloc(capacidad * sizeof(THNodo *));
if (tabla->buckets) {
for (int i = 0; i < capacidad; i++) {
tabla->buckets[i] = NULL;
}
} else {
tabla->capacidad = 0; // Error de memoria
}
}Insertar o actualizar · código C
Calcula bucket hash e inserta/actualiza clave-valor, gestionando colisiones por encadenamiento.
/**
* @brief Inserta un par clave-valor, actualizando el valor si la clave existe.
* @param[in,out] tabla Puntero a la tabla hash.
* @param[in] clave Clave a insertar.
* @param[in] valor Valor asociado a la clave.
* @return true si se insertó o actualizó correctamente, false en error.
* @pre Tabla inicializada con buckets validos y cadenas propias vivas aciclicas, contadores consistentes; cantidad+1 cabe en int al insertar clave nueva.
* @note NULL tabla/buckets o capacidad <= 0 retorna false. Actualizar no reserva ni cambia cantidad; insertar publica nodo propio al inicio. Fallo malloc retorna false sin mutacion. Capacidad fija: no rehash.
*/
bool th_insertar(TablaHash *tabla, int clave, int valor) {
if (!tabla || !tabla->buckets || tabla->capacidad <= 0) return false;
int indice = th_indice(tabla, clave);
THNodo *actual = tabla->buckets[indice];
// Buscar si ya existe para actualizar
while (actual != NULL) {
if (actual->clave == clave) {
actual->valor = valor;
return true;
}
actual = actual->siguiente;
}
// No existe, insertar al principio del bucket
THNodo *nuevo = (THNodo *)malloc(sizeof(THNodo));
if (!nuevo) return false;
nuevo->clave = clave;
nuevo->valor = valor;
nuevo->siguiente = tabla->buckets[indice];
tabla->buckets[indice] = nuevo;
tabla->cantidad++;
return true;
}
/**
* @brief Normaliza el módulo de una clave, incluidas claves negativas.
* @param tabla Tabla con capacidad positiva.
* @param clave Clave entera.
* @return Índice entre 0 y capacidad-1, o -1 ante tabla NULL o capacidad no positiva.
* @note Consulta capacidad sin acceder a buckets; admite INT_MIN y claves negativas. No reserva, libera ni modifica la tabla.
*/
int th_indice(const TablaHash *tabla, int clave) {
if (!tabla || tabla->capacidad <= 0) return -1;
int indice = clave % tabla->capacidad;
if (indice < 0) {
indice += tabla->capacidad;
}
return indice;
}Buscar clave · código C
Busca una clave y retorna su valor asociado si existe en la tabla.
/**
* @brief Busca el valor asociado a una clave.
* @param[in] tabla Puntero a la tabla hash constante.
* @param[in] clave Clave a buscar.
* @param[out] valor Puntero donde se almacenará el valor si se encuentra.
* @return true si se encontró la clave, false si no.
* @pre Tabla inicializada con cadenas vivas aciclicas; valor es int externo escribible sin solaparse con nodos/metadata.
* @note NULL tabla/buckets/valor o capacidad <= 0 retorna false. False no escribe salida. No reserva/libera/imprime ni cambia tabla bajo precondiciones.
*/
bool th_buscar(const TablaHash *tabla, int clave, int *valor) {
if (!tabla || !tabla->buckets || tabla->capacidad <= 0 || !valor) return false;
int indice = th_indice(tabla, clave);
THNodo *actual = tabla->buckets[indice];
while (actual != NULL) {
if (actual->clave == clave) {
*valor = actual->valor;
return true;
}
actual = actual->siguiente;
}
return false;
}
/**
* @brief Normaliza el módulo de una clave, incluidas claves negativas.
* @param tabla Tabla con capacidad positiva.
* @param clave Clave entera.
* @return Índice entre 0 y capacidad-1, o -1 ante tabla NULL o capacidad no positiva.
* @note Consulta capacidad sin acceder a buckets; admite INT_MIN y claves negativas. No reserva, libera ni modifica la tabla.
*/
int th_indice(const TablaHash *tabla, int clave) {
if (!tabla || tabla->capacidad <= 0) return -1;
int indice = clave % tabla->capacidad;
if (indice < 0) {
indice += tabla->capacidad;
}
return indice;
}Comprobar existencia · código C
Verifica existencia de una clave sin extraer ni modificar su valor.
/**
* @brief Verifica si una clave existe en la tabla.
* @param[in] tabla Puntero a la tabla hash.
* @param[in] clave Clave a verificar.
* @return true si la clave existe, false si no.
* @pre Tabla inicializada con buckets y cadenas vivas aciclicas o NULL.
* @note Delega a th_buscar con salida local; false para tabla no utilizable. No reserva/libera ni imprime.
*/
bool th_contiene(const TablaHash *tabla, int clave) {
int dummy_valor;
return th_buscar(tabla, clave, &dummy_valor);
}
/**
* @brief Normaliza el módulo de una clave, incluidas claves negativas.
* @param tabla Tabla con capacidad positiva.
* @param clave Clave entera.
* @return Índice entre 0 y capacidad-1, o -1 ante tabla NULL o capacidad no positiva.
* @note Consulta capacidad sin acceder a buckets; admite INT_MIN y claves negativas. No reserva, libera ni modifica la tabla.
*/
int th_indice(const TablaHash *tabla, int clave) {
if (!tabla || tabla->capacidad <= 0) return -1;
int indice = clave % tabla->capacidad;
if (indice < 0) {
indice += tabla->capacidad;
}
return indice;
}
/**
* @brief Busca el valor asociado a una clave.
* @param[in] tabla Puntero a la tabla hash constante.
* @param[in] clave Clave a buscar.
* @param[out] valor Puntero donde se almacenará el valor si se encuentra.
* @return true si se encontró la clave, false si no.
* @pre Tabla inicializada con cadenas vivas aciclicas; valor es int externo escribible sin solaparse con nodos/metadata.
* @note NULL tabla/buckets/valor o capacidad <= 0 retorna false. False no escribe salida. No reserva/libera/imprime ni cambia tabla bajo precondiciones.
*/
bool th_buscar(const TablaHash *tabla, int clave, int *valor) {
if (!tabla || !tabla->buckets || tabla->capacidad <= 0 || !valor) return false;
int indice = th_indice(tabla, clave);
THNodo *actual = tabla->buckets[indice];
while (actual != NULL) {
if (actual->clave == clave) {
*valor = actual->valor;
return true;
}
actual = actual->siguiente;
}
return false;
}Eliminar clave · código C
Elimina una clave del bucket correspondiente y ajusta enlaces de la cadena.
/**
* @brief Elimina una clave de la tabla hash.
* @param[in,out] tabla Puntero a la tabla hash.
* @param[in] clave Clave a eliminar.
* @return true si fue eliminada, false si no existía o error.
* @pre Tabla inicializada, propia y consistente; cadenas vivas aciclicas sin ownership compartido.
* @note NULL tabla/buckets o capacidad <= 0 retorna false. Elimina primera coincidencia, libera un nodo y decrementa cantidad; ausencia conserva tabla. Aliases a nodo liberado quedan invalidos. No rehash.
*/
bool th_eliminar(TablaHash *tabla, int clave) {
if (!tabla || !tabla->buckets || tabla->capacidad <= 0) return false;
int indice = th_indice(tabla, clave);
THNodo *actual = tabla->buckets[indice];
THNodo *anterior = NULL;
while (actual != NULL) {
if (actual->clave == clave) {
if (anterior == NULL) {
tabla->buckets[indice] = actual->siguiente;
} else {
anterior->siguiente = actual->siguiente;
}
free(actual);
tabla->cantidad--;
return true;
}
anterior = actual;
actual = actual->siguiente;
}
return false;
}
/**
* @brief Normaliza el módulo de una clave, incluidas claves negativas.
* @param tabla Tabla con capacidad positiva.
* @param clave Clave entera.
* @return Índice entre 0 y capacidad-1, o -1 ante tabla NULL o capacidad no positiva.
* @note Consulta capacidad sin acceder a buckets; admite INT_MIN y claves negativas. No reserva, libera ni modifica la tabla.
*/
int th_indice(const TablaHash *tabla, int clave) {
if (!tabla || tabla->capacidad <= 0) return -1;
int indice = clave % tabla->capacidad;
if (indice < 0) {
indice += tabla->capacidad;
}
return indice;
}Listar claves · código C
Devuelve todas las claves activas de la tabla.
/* Texto C de tabla completa; la proyeccion API es independiente. */
char buffer[2048];
th_formatear(&tabla, buffer, sizeof(buffer));
/**
* @brief Formatea el contenido de la tabla hash en una cadena de texto.
* @param[in] tabla Puntero a la tabla hash.
* @param[out] destino Buffer donde se escribirá la representación.
* @param[in] capacidad Tamaño máximo del buffer destino.
* @pre Buffer externo de al menos capacidad bytes sin solaparse con tabla/nodos; tabla inicializada y cadenas vivas aciclicas si no NULL.
* @note NULL destino o capacidad cero no escriben. Buffer valido positivo termina en NUL; trunca a capacidad-1 caracteres y capacidad 1 produce cadena vacia. Tabla/buckets NULL escribe Tabla no inicializada. No reserva/libera ni imprime stdout.
*/
void th_formatear(const TablaHash *tabla, char *destino, size_t capacidad) {
if (!destino || capacidad == 0) return;
destino[0] = '\0';
if (!tabla || !tabla->buckets) {
snprintf(destino, capacidad, "Tabla no inicializada\n");
return;
}
size_t usado = 0;
for (int i = 0; i < tabla->capacidad && usado < capacidad; i++) {
th_append_text(destino, capacidad, &usado, "[%d] -> ", i);
THNodo *actual = tabla->buckets[i];
while (actual != NULL && usado < capacidad) {
th_append_text(destino, capacidad, &usado, "(%d:%d) -> ", actual->clave, actual->valor);
actual = actual->siguiente;
}
if (usado < capacidad) {
th_append_text(destino, capacidad, &usado, "NULL\n");
}
}
}
/**
* @brief Ajunta texto formateado dentro de un buffer acotado.
* @param destino Buffer de salida.
* @param capacidad Tamaño total del buffer, incluido el terminador.
* @param usado Contador de bytes usados que se actualiza o satura a capacidad.
* @param fmt Formato de vsnprintf para los argumentos variables.
* @pre Buffer externo escribible de capacidad bytes; usado es size_t escribible; formato/argumentos validos de vsnprintf; salidas/formato no se solapan.
* @note NULL destino/usado o *usado >= capacidad se ignoran. Error vsnprintf no incrementa usado; truncamiento lo satura a capacidad, no a longitud real. No reserva/libera ni imprime stdout.
*/
static void th_append_text(char *destino, size_t capacidad, size_t *usado, const char *fmt, ...) {
va_list args;
int escritos;
if (destino == NULL || usado == NULL || *usado >= capacidad) {
return;
}
va_start(args, fmt);
escritos = vsnprintf(destino + *usado, capacidad - *usado, fmt, args);
va_end(args);
if (escritos < 0) {
return;
}
if ((size_t)escritos >= capacidad - *usado) {
*usado = capacidad;
} else {
*usado += (size_t)escritos;
}
}Listar valores · código C
Devuelve todos los valores almacenados actualmente.
/* Texto C de tabla completa; la proyeccion API es independiente. */
char buffer[2048];
th_formatear(&tabla, buffer, sizeof(buffer));
/**
* @brief Formatea el contenido de la tabla hash en una cadena de texto.
* @param[in] tabla Puntero a la tabla hash.
* @param[out] destino Buffer donde se escribirá la representación.
* @param[in] capacidad Tamaño máximo del buffer destino.
* @pre Buffer externo de al menos capacidad bytes sin solaparse con tabla/nodos; tabla inicializada y cadenas vivas aciclicas si no NULL.
* @note NULL destino o capacidad cero no escriben. Buffer valido positivo termina en NUL; trunca a capacidad-1 caracteres y capacidad 1 produce cadena vacia. Tabla/buckets NULL escribe Tabla no inicializada. No reserva/libera ni imprime stdout.
*/
void th_formatear(const TablaHash *tabla, char *destino, size_t capacidad) {
if (!destino || capacidad == 0) return;
destino[0] = '\0';
if (!tabla || !tabla->buckets) {
snprintf(destino, capacidad, "Tabla no inicializada\n");
return;
}
size_t usado = 0;
for (int i = 0; i < tabla->capacidad && usado < capacidad; i++) {
th_append_text(destino, capacidad, &usado, "[%d] -> ", i);
THNodo *actual = tabla->buckets[i];
while (actual != NULL && usado < capacidad) {
th_append_text(destino, capacidad, &usado, "(%d:%d) -> ", actual->clave, actual->valor);
actual = actual->siguiente;
}
if (usado < capacidad) {
th_append_text(destino, capacidad, &usado, "NULL\n");
}
}
}
/**
* @brief Ajunta texto formateado dentro de un buffer acotado.
* @param destino Buffer de salida.
* @param capacidad Tamaño total del buffer, incluido el terminador.
* @param usado Contador de bytes usados que se actualiza o satura a capacidad.
* @param fmt Formato de vsnprintf para los argumentos variables.
* @pre Buffer externo escribible de capacidad bytes; usado es size_t escribible; formato/argumentos validos de vsnprintf; salidas/formato no se solapan.
* @note NULL destino/usado o *usado >= capacidad se ignoran. Error vsnprintf no incrementa usado; truncamiento lo satura a capacidad, no a longitud real. No reserva/libera ni imprime stdout.
*/
static void th_append_text(char *destino, size_t capacidad, size_t *usado, const char *fmt, ...) {
va_list args;
int escritos;
if (destino == NULL || usado == NULL || *usado >= capacidad) {
return;
}
va_start(args, fmt);
escritos = vsnprintf(destino + *usado, capacidad - *usado, fmt, args);
va_end(args);
if (escritos < 0) {
return;
}
if ((size_t)escritos >= capacidad - *usado) {
*usado = capacidad;
} else {
*usado += (size_t)escritos;
}
}Listar pares · código C
Devuelve pares clave-valor para inspección completa del contenido.
/* Texto C de tabla completa; la proyeccion API es independiente. */
char buffer[2048];
th_formatear(&tabla, buffer, sizeof(buffer));
/**
* @brief Formatea el contenido de la tabla hash en una cadena de texto.
* @param[in] tabla Puntero a la tabla hash.
* @param[out] destino Buffer donde se escribirá la representación.
* @param[in] capacidad Tamaño máximo del buffer destino.
* @pre Buffer externo de al menos capacidad bytes sin solaparse con tabla/nodos; tabla inicializada y cadenas vivas aciclicas si no NULL.
* @note NULL destino o capacidad cero no escriben. Buffer valido positivo termina en NUL; trunca a capacidad-1 caracteres y capacidad 1 produce cadena vacia. Tabla/buckets NULL escribe Tabla no inicializada. No reserva/libera ni imprime stdout.
*/
void th_formatear(const TablaHash *tabla, char *destino, size_t capacidad) {
if (!destino || capacidad == 0) return;
destino[0] = '\0';
if (!tabla || !tabla->buckets) {
snprintf(destino, capacidad, "Tabla no inicializada\n");
return;
}
size_t usado = 0;
for (int i = 0; i < tabla->capacidad && usado < capacidad; i++) {
th_append_text(destino, capacidad, &usado, "[%d] -> ", i);
THNodo *actual = tabla->buckets[i];
while (actual != NULL && usado < capacidad) {
th_append_text(destino, capacidad, &usado, "(%d:%d) -> ", actual->clave, actual->valor);
actual = actual->siguiente;
}
if (usado < capacidad) {
th_append_text(destino, capacidad, &usado, "NULL\n");
}
}
}
/**
* @brief Ajunta texto formateado dentro de un buffer acotado.
* @param destino Buffer de salida.
* @param capacidad Tamaño total del buffer, incluido el terminador.
* @param usado Contador de bytes usados que se actualiza o satura a capacidad.
* @param fmt Formato de vsnprintf para los argumentos variables.
* @pre Buffer externo escribible de capacidad bytes; usado es size_t escribible; formato/argumentos validos de vsnprintf; salidas/formato no se solapan.
* @note NULL destino/usado o *usado >= capacidad se ignoran. Error vsnprintf no incrementa usado; truncamiento lo satura a capacidad, no a longitud real. No reserva/libera ni imprime stdout.
*/
static void th_append_text(char *destino, size_t capacidad, size_t *usado, const char *fmt, ...) {
va_list args;
int escritos;
if (destino == NULL || usado == NULL || *usado >= capacidad) {
return;
}
va_start(args, fmt);
escritos = vsnprintf(destino + *usado, capacidad - *usado, fmt, args);
va_end(args);
if (escritos < 0) {
return;
}
if ((size_t)escritos >= capacidad - *usado) {
*usado = capacidad;
} else {
*usado += (size_t)escritos;
}
}Estadísticas · código C
Calcula métricas de carga, buckets usados y colisiones para diagnóstico.
/* C float y texto; la proyeccion API mantiene su cociente propio. */
THEstadisticas stats = th_estadisticas(&tabla);
char texto[512];
th_formatear_estadisticas(&tabla, texto, sizeof(texto));
/**
* @brief Calcula y retorna las estadísticas de la tabla hash.
* @param[in] tabla Puntero a la tabla hash.
* @return Estructura con las estadísticas calculadas.
* @pre Si utilizable, capacidad entradas de buckets, cadenas vivas aciclicas y contadores representables en int.
* @note Devuelve todos los campos cero ante tabla/buckets NULL o capacidad <= 0. Cantidad se lee de metadata; factor_carga es cantidad/capacidad float. Colisiones suma nodos menos uno por bucket ocupado. No reserva/libera/modifica.
*/
THEstadisticas th_estadisticas(const TablaHash *tabla) {
THEstadisticas stats = {0};
if (!tabla || !tabla->buckets || tabla->capacidad <= 0) return stats;
stats.capacidad = tabla->capacidad;
stats.cantidad = tabla->cantidad;
stats.factor_carga = (float)tabla->cantidad / (float)tabla->capacidad;
for (int i = 0; i < tabla->capacidad; i++) {
THNodo *actual = tabla->buckets[i];
if (actual != NULL) {
stats.buckets_ocupados++;
int nodos_en_bucket = 0;
while (actual != NULL) {
nodos_en_bucket++;
actual = actual->siguiente;
}
if (nodos_en_bucket > 1) {
stats.colisiones += (nodos_en_bucket - 1);
}
}
}
return stats;
}
/**
* @brief Formatea las estadísticas de la tabla en una cadena de texto.
* @param[in] tabla Puntero a la tabla hash.
* @param[out] destino Buffer donde se escribirá la representación.
* @param[in] capacidad Tamaño máximo del buffer destino.
* @pre Buffer externo de al menos capacidad bytes sin solaparse con tabla/nodos; tabla inicializada y cadenas vivas aciclicas si no NULL.
* @note NULL destino/capacidad cero no escriben. Buffer valido positivo termina en NUL, truncando si hace falta. Tabla/buckets NULL escribe Tabla no inicializada. Factor de carga con dos decimales; no reserva/libera ni imprime stdout.
*/
void th_formatear_estadisticas(const TablaHash *tabla, char *destino, size_t capacidad) {
if (!destino || capacidad == 0) return;
destino[0] = '\0';
if (!tabla || !tabla->buckets) {
snprintf(destino, capacidad, "Tabla no inicializada\n");
return;
}
THEstadisticas stats = th_estadisticas(tabla);
snprintf(destino, capacidad,
"Capacidad: %d\n"
"Cantidad: %d\n"
"Buckets ocupados: %d\n"
"Colisiones: %d\n"
"Factor de carga: %.2f\n",
stats.capacidad, stats.cantidad, stats.buckets_ocupados,
stats.colisiones, stats.factor_carga);
}Vaciar tabla · código C
Limpia la tabla completa y reinicia su estado interno.
/**
* @brief Elimina todos los elementos manteniendo la capacidad de la tabla.
* @param[in,out] tabla Puntero a la tabla hash.
* @pre Tabla inicializada, propia y consistente con capacidad entradas de buckets y cadenas vivas aciclicas.
* @note NULL tabla/buckets no escribe ni normaliza metadata. Libera nodos, pone buckets individuales NULL y cantidad=0; conserva arreglo y capacidad. Aliases a nodos liberados quedan invalidos.
*/
void th_vaciar(TablaHash *tabla) {
if (!tabla || !tabla->buckets) return;
for (int i = 0; i < tabla->capacidad; i++) {
THNodo *actual = tabla->buckets[i];
while (actual != NULL) {
THNodo *siguiente = actual->siguiente;
free(actual);
actual = siguiente;
}
tabla->buckets[i] = NULL;
}
tabla->cantidad = 0;
}Destruir tabla · código C
Libera las cadenas de entradas y el arreglo de buckets. Se distingue de vaciar, que conserva los buckets para seguir usando la misma capacidad.
/**
* @brief Destruye la tabla hash liberando toda su memoria.
* @param[in,out] tabla Puntero a la tabla hash.
* @pre Tabla inicializada, propia y consistente; nodos y arreglo son reservas liberables sin compartir ownership.
* @note NULL tabla/buckets se ignoran sin normalizar metadata. Tras vaciar y liberar arreglo fija buckets=NULL, capacidad=cantidad=0. No libera estructura prestada. Aliases a nodos/arreglo quedan invalidos.
*/
void th_destruir(TablaHash *tabla) {
if (!tabla || !tabla->buckets) return;
th_vaciar(tabla);
free(tabla->buckets);
tabla->buckets = NULL;
tabla->capacidad = 0;
tabla->cantidad = 0;
}
/**
* @brief Elimina todos los elementos manteniendo la capacidad de la tabla.
* @param[in,out] tabla Puntero a la tabla hash.
* @pre Tabla inicializada, propia y consistente con capacidad entradas de buckets y cadenas vivas aciclicas.
* @note NULL tabla/buckets no escribe ni normaliza metadata. Libera nodos, pone buckets individuales NULL y cantidad=0; conserva arreglo y capacidad. Aliases a nodos liberados quedan invalidos.
*/
void th_vaciar(TablaHash *tabla) {
if (!tabla || !tabla->buckets) return;
for (int i = 0; i < tabla->capacidad; i++) {
THNodo *actual = tabla->buckets[i];
while (actual != NULL) {
THNodo *siguiente = actual->siguiente;
free(actual);
actual = siguiente;
}
tabla->buckets[i] = NULL;
}
tabla->cantidad = 0;
}Destruir y recrear (receta del llamador) · código C
Muestra dos funciones para destruir la tabla anterior e inicializar otra con capacidad válida. Es una receta del llamador, no un algoritmo adicional ni un botón independiente.
/**
* @brief Destruye la tabla hash liberando toda su memoria.
* @param[in,out] tabla Puntero a la tabla hash.
* @pre Tabla inicializada, propia y consistente; nodos y arreglo son reservas liberables sin compartir ownership.
* @note NULL tabla/buckets se ignoran sin normalizar metadata. Tras vaciar y liberar arreglo fija buckets=NULL, capacidad=cantidad=0. No libera estructura prestada. Aliases a nodos/arreglo quedan invalidos.
*/
void th_destruir(TablaHash *tabla) {
if (!tabla || !tabla->buckets) return;
th_vaciar(tabla);
free(tabla->buckets);
tabla->buckets = NULL;
tabla->capacidad = 0;
tabla->cantidad = 0;
}
/* Reinicio recomendado del TAD conservando una capacidad valida. */
/**
* @brief Inicializa la tabla hash.
* @param[out] tabla Puntero a la tabla hash.
* @param[in] capacidad Capacidad (número de buckets) de la tabla.
* @pre Estructura sin recursos propietarios pendientes; capacidad * sizeof(THNodo*) debe caber en size_t.
* @note NULL tabla o capacidad <= 0 retorna sin escribir: no inicializa una estructura indeterminada. No libera recursos previos: destruir antes de reinicializar.
* @note Con capacidad positiva fija cantidad=0; malloc fallido deja buckets=NULL y capacidad=0. Retorno void no comunica exito. No rehash.
*/
void th_inicializar(TablaHash *tabla, int capacidad) {
if (!tabla || capacidad <= 0) return;
tabla->capacidad = capacidad;
tabla->cantidad = 0;
tabla->buckets = (THNodo **)malloc(capacidad * sizeof(THNodo *));
if (tabla->buckets) {
for (int i = 0; i < capacidad; i++) {
tabla->buckets[i] = NULL;
}
} else {
tabla->capacidad = 0; // Error de memoria
}
}