Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion faq/hash.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ Debe indicar que llegó al final cuando ya no queden listas por recorrer.

No. Las funciones de hashing son un tema muy extenso en la computación, y no se pretende que se codifique una. Cabe destacar que estas pueden llegar a impactar sobre el rendimiento de la tabla de hash, así que de encontrarse con un hash que no rinde como uno esperaría, uno de las primeras cosas a probar es cambiar la función de hash.

## ¿Por qué la capacidad de una tabla de hash debe ser un número primo?
## ¿Qué es esto de la función de igualdad?

En Go no podemos usar el operador `==` para tipos de dato `any`. Entonces, necesitamos de _algo_ que nos indique si dos claves son en efecto equivalentes (lo que haríamos con dicho `==`). Por lo tanto, lo que hacemos es recibir una función por parámetro al crear el hash que justamente nos determine si dos claves son en efecto iguales.

Anteriormente (hasta 2025C1), para evitar esto, lo que se hacía es que el hash no trabaje con tipos de dato `any` como claves, sino con `comparable`, que es una restricción de Go que permite únicamente usar tipos de datos que soporten el uso del operador `==`. Esto traía algunas confusiones y problemas, por lo que desde 2025C2 decidimos cambiar esto por la función de equivalencia.

## ¿Por qué la capacidad de una tabla de hash puede ser conveniente que sea un número primo?

Para obtener el mejor rendimiento de nuestra tabla de hash, queremos que las claves esten lo mejor distribuidas posibles en la capacidad. Es decir, que reduzcamos al mínimo posible las colisiones que obtendremos. Recordemos que una colisión es cuando dos claves van a parar al mismo 'balde' de la tabla de hash, sin importar si es abierto o cerrado.

Expand Down Expand Up @@ -68,6 +74,19 @@ Entonces, estamos en búsqueda de un d que contenga la menor cantidad posible de

Cabe destacar que si las claves originales vienen distribuidas uniformemente, esto no soluciona el problema, porque nos dara resultados similares para cualquier capacidad utilizada. Pero, como en la mayoría de los casos las claves no vienen con distribución específica, terminamos concluyendo que lo mejor es usar un número primo como capacidad de la tabla de hash.

## ¿Cómo podemos asegurar que al redimensionar mantengamos un tamaño primo?

En sí hay dos formas simples de hacer esto:

1. Duplicar tamaño y luego, mientras el valor no sea un número primo, le sumamos 1.
2. Tener una tabla de números primos cargada ya de antemano en el hash (que sea un arreglo constante ya precalculado).

El problema de la opción 1 es que nos hace perder toda ganancia. Determinar si un número es primo es al menos $$\mathcal{O}\left(\sqrt{n})$$. Si eso lo aplicamos hasta obtener un siguiente número primo, esto puede demorar demasiado, haciéndonos perder cualquier mínima ventaja que podríamos tener por utilizar un número primo.

La segunda opción implica tener estado en el hash recordando en cuál índice de dicho arreglo estamos, y obviamente hacer un arreglo que pueda tener todos los posibles tamaños que pueda manejar el hash.
Copy link
Contributor

@fmesteban fmesteban Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si guardamos primos mas o menos el doble de tamaño que el anterior deberíamos guardar no más de 64 primos, no me parece tan grave.

Igual esta bien dejar la consigna como está, pero si viene algún loquito del hash de nuevo y lo quiere probar podría.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claro, por eso digo que hay que armar la tabla. Este cambio en el FAQ en sí no tiene que ver con el cambio, pero hacía rato que faltaba


En general, esta ventaja que obtenemos es mínima y no vale la pena la implementación de la opción 2 para los fines del curso y los usos que les damos al hash, por lo que simplificamos y simplemente duplicamos el tamaño de la tabla y listo.

## ¿Por qué al redimensionar no puedo crear un hash nuevo?

Lo primero a entender es que no podemos redimensionar sobre la misma tabla que estábamos antes. De una forma u otra, necesitamos poder re-hashear los elementos sobre una tabla.
Expand Down
6 changes: 3 additions & 3 deletions tda/abb.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ El trabajo que deben entregar de **forma grupal** es el tipo de dato abstracto
Tanto el DiccionarioOrdenado como el ABB deben estar también dentro del paquete `diccionario`
Se incluye en [el sitio de descargas]({{site.skel}}) el archivo `diccionario_ordenado.go` que se describe a continuación:

``` cpp
type DiccionarioOrdenado[K comparable, V any] interface {
```golang
type DiccionarioOrdenado[K any, V any] interface {
Diccionario[K, V]

IterarRango(desde *K, hasta *K, visitar func(clave K, dato V) bool)
Expand All @@ -26,7 +26,7 @@ Todas las primitivas anteriores deben funcionar también, con el agregado que ta

Además, la primitiva de creación del ABB deberá ser:
```golang
func CrearABB[K comparable, V any](funcion_cmp func(K, K) int) DiccionarioOrdenado[K, V]
func CrearABB[K any, V any](funcionCmp func(K, K) int) DiccionarioOrdenado[K, V]
```

La función de comparación, recibe dos claves y devuelve:
Expand Down
10 changes: 6 additions & 4 deletions tda/hash.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ El trabajo que deben entregar de **forma grupal** es el tipo abstracto de datos

#### Primitivas del Diccionario
```GoLang
type Diccionario[K comparable, V any] interface {
type Diccionario[K any, V any] interface {
Guardar(clave K, dato V)
Pertenece(clave K) bool
Obtener(clave K) V
Expand All @@ -27,17 +27,19 @@ Tanto `Borrar` como `Obtener` deben entrar en pánico con el mensaje `'La clave

Además, la primitiva de creación del *hash* deberá ser:
```GoLang
func CrearHash[K comparable, V any]() Diccionario[K, V]
func CrearHash[K any, V any](func(K, K) bool) Diccionario[K, V]
```

La función pasada por parámetro es una que indica si dos claves son en efecto la misma clave (dado que no es posible usar `==` con datos genéricos en Go, salvo que restrinjamos las claves a tipos de datos `comparable`s).

Nuevamente, el iterador interno (`Iterar`) debe iterar internamente el *hash*, aplicando la función pasada por parámetro a la clave y los datos.

#### Función de *hashing*... ¿Genérica?

Es de considerar que para implementar el *hash* será necesario definir una función de *hashing* internamente. Pueden definir la que más les guste (siempre poniendo referencia o nombre de la misma). Lamentablemente, no podemos trabajar de forma completamente genérica con una función de *hashing* directamente, por lo que deberemos realizar una transformación.
Si bien no es obligatorio pasar la clave a un arreglo de *bytes* (`[]byte`), es lo recomendado. Luego, la función de *hashing* puede siempre trabajar con la versión de arreglo de *bytes* correspondiente a la clave. El siguiente código (que pueden utilizar, modificar, o lo que gusten) transforma un tipo de dato genérico a un *array* de *bytes*:
```GoLang
func convertirABytes[K comparable](clave K) []byte {
func convertirABytes[K any](clave K) []byte {
return []byte(fmt.Sprintf("%v", clave))
}
```
Expand All @@ -46,7 +48,7 @@ Para utilizar [`fmt.Sprintf`](https://pkg.go.dev/fmt#Sprintf), se debe importar

#### Primitivas del iterador
```GoLang
type IterDiccionario[K comparable, V any] interface {
type IterDiccionario[K any, V any] interface {
HaySiguiente() bool
VerActual() (K, V)
Siguiente()
Expand Down