c++ - Hash un valor de precisión arbitrario(boost:: multiprecision:: cpp_int)
c++11 arbitrary-precision (2)
Necesito obtener el hash de valor con precisión arbitraria (de Boost.Multiprecision);
Yo uso el backend
cpp_int
.
Por ahora, se me ocurrió el siguiente código:
boost::multiprecision::cpp_int x0 = 1;
const auto seed = std::hash<std::string>{}(x0.str());
No necesito que el código sea lo más rápido posible, pero me resulta muy torpe manipular la representación de cadena.
Entonces mi pregunta es doble:
- Manteniendo la precisión arbitraria, ¿puedo cambiar el valor de manera más eficiente?
-
¿Tal vez no debería insistir en mantener la precisión arbitraria y debería convertir a un
double
que podría calcular fácilmente (sin embargo, aún haría la comparación necesaria para la tabla hash utilizando el valor de precisión arbitraria)?
Puede (ab) usar el soporte de serialización:
El soporte para la serialización viene en dos formas: el
number
clases,debug_adaptor
,logged_adaptor
yrational_adaptor
tienen soporte de serialización "pasante" que requiere que el backend subyacente sea serializable.Los backends
cpp_int
,cpp_bin_float
,cpp_dec_float
yfloat128
tienen soporte completo para Boost.Serialization.
Entonces, permítanme improvisar algo que funcione con impulso y contenedores estándar no ordenados:
template <typename Map>
void test(Map const& map) {
std::cout << "/n" << __PRETTY_FUNCTION__ << "/n";
for(auto& p : map)
std::cout << p.second << "/t" << p.first << "/n";
}
int main() {
using boost::multiprecision::cpp_int;
test(std::unordered_map<cpp_int, std::string> {
{ cpp_int(1) << 111, "one" },
{ cpp_int(2) << 222, "two" },
{ cpp_int(3) << 333, "three" },
});
test(boost::unordered_map<cpp_int, std::string> {
{ cpp_int(1) << 111, "one" },
{ cpp_int(2) << 222, "two" },
{ cpp_int(3) << 333, "three" },
});
}
Vamos a reenviar las implementaciones
hash<>
relevantes a nuestra propia especialización
hash_impl
que usa Multiprecisión
y
Serialización:
namespace std {
template <typename backend>
struct hash<boost::multiprecision::number<backend> >
: mp_hashing::hash_impl<boost::multiprecision::number<backend> >
{};
}
namespace boost {
template <typename backend>
struct hash<multiprecision::number<backend> >
: mp_hashing::hash_impl<multiprecision::number<backend> >
{};
}
Ahora, por supuesto, esto plantea la pregunta, ¿cómo se implementa
hash_impl
?
template <typename T> struct hash_impl {
size_t operator()(T const& v) const {
using namespace boost;
size_t seed = 0;
{
iostreams::stream<hash_sink> os(seed);
archive::binary_oarchive oa(os, archive::no_header | archive::no_codecvt);
oa << v;
}
return seed;
}
};
Esto se ve bastante simple.
Esto se debe a que Boost es increíble, y escribir un dispositivo
hash_sink
para usar con Boost Iostreams es solo el siguiente ejercicio sencillo:
namespace io = boost::iostreams;
struct hash_sink {
hash_sink(size_t& seed_ref) : _ptr(&seed_ref) {}
typedef char char_type;
typedef io::sink_tag category;
std::streamsize write(const char* s, std::streamsize n) {
boost::hash_combine(*_ptr, boost::hash_range(s, s+n));
return n;
}
private:
size_t* _ptr;
};
Demo completa:
#include <iostream>
#include <iomanip>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/cpp_int/serialize.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/functional/hash.hpp>
namespace mp_hashing {
namespace io = boost::iostreams;
struct hash_sink {
hash_sink(size_t& seed_ref) : _ptr(&seed_ref) {}
typedef char char_type;
typedef io::sink_tag category;
std::streamsize write(const char* s, std::streamsize n) {
boost::hash_combine(*_ptr, boost::hash_range(s, s+n));
return n;
}
private:
size_t* _ptr;
};
template <typename T> struct hash_impl {
size_t operator()(T const& v) const {
using namespace boost;
size_t seed = 0;
{
iostreams::stream<hash_sink> os(seed);
archive::binary_oarchive oa(os, archive::no_header | archive::no_codecvt);
oa << v;
}
return seed;
}
};
}
#include <unordered_map>
#include <boost/unordered_map.hpp>
namespace std {
template <typename backend>
struct hash<boost::multiprecision::number<backend> >
: mp_hashing::hash_impl<boost::multiprecision::number<backend> >
{};
}
namespace boost {
template <typename backend>
struct hash<multiprecision::number<backend> >
: mp_hashing::hash_impl<multiprecision::number<backend> >
{};
}
template <typename Map>
void test(Map const& map) {
std::cout << "/n" << __PRETTY_FUNCTION__ << "/n";
for(auto& p : map)
std::cout << p.second << "/t" << p.first << "/n";
}
int main() {
using boost::multiprecision::cpp_int;
test(std::unordered_map<cpp_int, std::string> {
{ cpp_int(1) << 111, "one" },
{ cpp_int(2) << 222, "two" },
{ cpp_int(3) << 333, "three" },
});
test(boost::unordered_map<cpp_int, std::string> {
{ cpp_int(1) << 111, "one" },
{ cpp_int(2) << 222, "two" },
{ cpp_int(3) << 333, "three" },
});
}
Huellas dactilares
void test(const Map&) [with Map = std::unordered_map<boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<> >, std::basic_string<char> >]
one 2596148429267413814265248164610048
three 52494017394792286184940053450822912768476066341437098474218494553838871980785022157364316248553291776
two 13479973333575319897333507543509815336818572211270286240551805124608
void test(const Map&) [with Map = boost::unordered::unordered_map<boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<> >, std::basic_string<char> >]
three 52494017394792286184940053450822912768476066341437098474218494553838871980785022157364316248553291776
two 13479973333575319897333507543509815336818572211270286240551805124608
one 2596148429267413814265248164610048
Como puede ver, la diferencia en la implementación entre unordered_map de Boost y la biblioteca estándar se muestra en los diferentes ordenamientos para hashes idénticos.
Solo para decir que acabo de agregar soporte de hashing nativo (para Boost.Hash y std :: hash) para desarrollar git. Funciona para todos los tipos de números, incluidos los de GMP, etc. Desafortunadamente, ese código no se lanzará hasta Boost-1.62 ahora.
La respuesta anterior que (ab) usa soporte de serialización, en realidad es extremadamente genial y bastante inteligente;) Sin embargo, no funcionaría si quisieras usar un hasher basado en vectores como CityHash, agregué un ejemplo de usar eso accediendo las extremidades directamente a los documentos: https://htmlpreview.github.io/?https://github.com/boostorg/multiprecision/blob/develop/doc/html/boost_multiprecision/tut/hash.html Acceso directo a las extremidades o la sugerencia de serialización funcionará con todas las versiones anteriores, por supuesto.