Funciones hash personalizadas
Calcule el hash de sus propios tipos
Funciones hash personalizadas es una lección gratuita de C++ Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C++ Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C++ Academy incluye 4 lecciones en total.
¿Por qué usar hashes personalizados?
Los contenedores no ordenados necesitan una forma de aplicar hash a sus claves. Los tipos integrados y std::string ya tienen hashes, pero sus propios tipos no. Debe proporcionar uno.
#include <iostream>
#include <unordered_set>
#include <string>
int main() {
std::unordered_set<std::string> s{"hi"};
std::cout << s.count("hi") << '\n';
return 0;
}La plantilla std::hash
std::hash es un functor que asigna un valor a un size_t. Se invoca como una función.
#include <iostream>
#include <functional>
#include <string>
int main() {
std::hash<std::string> h;
std::cout << "hash exists and returns a size_t\n";
std::size_t v = h("hello");
std::cout << (v != 0 ? "non-zero hash" : "zero") << '\n';
return 0;
}Un struct al que aplicar hash
Suponga que tenemos un Point con dos enteros. Para almacenarlo en un unordered_set necesitamos tanto igualdad como un hash.
#include <iostream>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
int main() {
Point a{1, 2}, b{1, 2};
std::cout << std::boolalpha << (a == b) << '\n';
return 0;
}Escribir un functor de hash
Un functor de hash es un struct cuyo operator() devuelve un size_t. Combine los hashes de los campos, normalmente mediante XOR y un desplazamiento.
#include <iostream>
#include <functional>
struct Point { int x, y; };
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
int main() {
PointHash h;
std::cout << "hashed: " << (h({3, 4}) != 0 ? "ok" : "zero") << '\n';
return 0;
}Usar el functor de hash
Pase el functor de hash como segundo argumento de plantilla del contenedor no ordenado.
#include <iostream>
#include <unordered_set>
#include <functional>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
int main() {
std::unordered_set<Point, PointHash> pts;
pts.insert({1, 2});
pts.insert({1, 2});
std::cout << pts.size() << '\n';
return 0;
}La igualdad también es necesaria
Dos claves terminan en el mismo bucket si sus hashes colisionan. El contenedor usa entonces operator== para distinguirlas, por lo que la igualdad es obligatoria.
#include <iostream>
#include <unordered_set>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x * 31 + p.y);
}
};
int main() {
std::unordered_set<Point, PointHash> s{{1, 1}, {2, 2}};
std::cout << s.count({1, 1}) << '\n';
return 0;
}Usar hash como clave de map
El mismo hash personalizado permite usar un struct como clave en un unordered_map.
#include <iostream>
#include <unordered_map>
#include <functional>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
int main() {
std::unordered_map<Point, std::string, PointHash> m;
m[{0, 0}] = "origin";
std::cout << m[{0, 0}] << '\n';
return 0;
}Combinar varios campos
Un helper habitual combina los hashes campo por campo mediante un patrón de multiplicación y suma similar a boost::hash_combine.
#include <iostream>
#include <functional>
std::size_t combine(std::size_t seed, std::size_t v) {
return seed ^ (v + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}
int main() {
std::size_t h = 0;
h = combine(h, std::hash<int>()(10));
h = combine(h, std::hash<int>()(20));
std::cout << (h != 0 ? "combined ok" : "zero") << '\n';
return 0;
}Buena distribución del hash
Un hash deficiente que devuelve una constante coloca todo en un solo bucket y degrada el rendimiento a O(n). Mezcle bien los bits de todos los campos.
#include <iostream>
#include <functional>
struct Bad { std::size_t operator()(int) const { return 0; } };
struct Good { std::size_t operator()(int x) const { return std::hash<int>()(x); } };
int main() {
std::cout << Bad()(5) << ' ' << (Good()(5) != 0 ? "varies" : "0") << '\n';
return 0;
}Especializar std::hash
Como alternativa, especialice std::hash para su tipo, de modo que funcione sin pasar un functor explícitamente.
#include <iostream>
#include <unordered_set>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
namespace std {
template <> struct hash<Point> {
std::size_t operator()(const Point& p) const {
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
}
int main() {
std::unordered_set<Point> s{{1, 2}};
std::cout << s.count({1, 2}) << '\n';
return 0;
}Una lambda como hash
En C++20 incluso puede usar una lambda sin estado como hash pasando su tipo.
#include <iostream>
#include <unordered_set>
int main() {
auto h = [](int x) { return std::hash<int>()(x * 2654435761u); };
std::unordered_set<int, decltype(h)> s(8, h);
s.insert(42);
std::cout << s.count(42) << '\n';
return 0;
}Comprobación rápida
Compruebe su comprensión de los hashes personalizados.
Resumen
Ha aprendido a aplicar hash a tipos personalizados:
- proporcione un functor de hash (o especialice
std::hash) que devuelva unsize_t - proporcione también operator== para distinguir las claves que colisionen
- combine correctamente los hashes de los campos para obtener una buena distribución
A continuación, explorará los buckets y el rendimiento del factor de carga.
Preguntas frecuentes
¿La lección «Funciones hash personalizadas» es gratis?
Sí — el texto completo de «Funciones hash personalizadas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C++ Academy, actualiza a CoddyKit PRO. El curso de C++ Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Funciones hash personalizadas»?
Calcule el hash de sus propios tipos Practicas C++ Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar C++ Academy?
No se requiere experiencia previa. C++ Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Funciones hash personalizadas»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de C++ Academy?
Sí. Cada lección de C++ Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- std::unordered_map
- unordered_set
- Funciones hash personalizadas
- Consideraciones de rendimiento