Хешируйте произвольное значение точности (boost::multiprecision::cpp_int)

Мне нужно получить хэш значения с произвольной точностью (из Boost.Multiprecision); Я использую cpp_int бэкенд. На данный момент я придумал следующий код:

boost::multiprecision::cpp_int x0 = 1;
const auto seed = std::hash<std::string>{}(x0.str());

Мне не нужно, чтобы код был настолько быстрым, насколько это возможно, но я считаю очень неуклюжим хешировать строковое представление.

Итак, мой вопрос состоит из двух частей:

  • Сохраняя произвольную точность, могу ли я хэшировать значение более эффективно?
  • Может быть, я не должен настаивать на сохранении произвольной точности, и я должен преобразовать в double который я мог бы легко хэшировать (однако я все равно делал бы сравнение, необходимое для хеш-таблицы, используя произвольное значение точности)?

2 ответа

Решение

Вы можете (ab) использовать поддержку сериализации:

Поддержка сериализации поставляется в двух формах: Классы number, debug_adaptor, logged_adaptor а также rational_adaptor иметь "сквозную" поддержку сериализации, которая требует, чтобы базовый бэкэнд был сериализуемым.

Backends cpp_int, cpp_bin_float, cpp_dec_float а также float128 есть полная поддержка Boost.Serialization.

Итак, позвольте мне объединить что-то, что работает с неупорядоченными контейнерами boost и std:

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" },
    });
}

Давайте отправим соответствующие hash<> реализации наших собственных hash_impl специализация, которая использует Multiprecision и Serialization:

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> > 
    {};
}

Теперь, конечно, напрашивается вопрос, как 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;
    }
};

Это выглядит довольно просто. Это потому, что Boost потрясающий, и написание hash_sink Устройство для использования с Boost Iostreams - это просто следующее простое упражнение:

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;
};

Полная демонстрация:

Жить на Колиру

#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" },
    });
}

Печать

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

Как видите, разница в реализации между Boost и стандартной библиотекой unordered_map показать в разных порядках для одинаковых хэшей.

Просто чтобы сказать, что я только что добавил встроенную поддержку хеширования (для Boost.Hash и std::hash) для разработки git. Он работает для всех типов чисел, в том числе из GMP и т. Д. К сожалению, этот код не будет выпущен до Boost-1.62.

Ответ выше, что (ab) использует поддержку сериализации, на самом деле очень крутой и действительно довольно умный;) Однако, это не сработает, если вы захотите использовать векторный хеш-код, такой как CityHash, я добавил пример использования этого путем доступа конечности непосредственно к документам: https://htmlpreview.github.io/?https://github.com/boostorg/multiprecision/blob/develop/doc/html/boost_multiprecision/tut/hash.html Либо прямой доступ к конечностям или совет сериализации будет работать со всеми предыдущими выпусками, конечно.

Другие вопросы по тегам