繁体   English   中英

散列任意精度值 (boost::multiprecision::cpp_int)

[英]Hash an arbitrary precision value (boost::multiprecision::cpp_int)

我需要获得任意精度的值的哈希值(来自 Boost.Multiprecision); 我使用cpp_int后端。 我想出了以下代码:

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

我不需要代码尽可能快,但我发现散列字符串表示非常笨拙。

所以我的问题是双重的:

  • 保持任意精度,我可以更有效地散列值吗?
  • 也许我不应该坚持保持任意精度,而应该转换为可以轻松散列的double精度值(但是,我仍然会使用任意精度值对散列表进行所需的比较)?

您可以(ab)使用序列化支持:

对序列化的支持有两种形式:类numberdebug_adaptorlogged_adaptorrational_adaptor具有“直通”序列化支持,这需要底层后端可序列化。

后端cpp_intcpp_bin_floatcpp_dec_floatfloat128完全支持 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专业化:

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_implhash_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 很棒,编写一个用于 Boost Iostreams 的hash_sink设备只是以下简单的练习:

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

完整演示:

住在 Coliru

#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之间的实现差异显示在相同散列的不同排序中。

只是说我刚刚为 git develop 添加了本机散列支持(用于 Boost.Hash 和 std::hash)。 它适用于所有数字类型,包括来自 GMP 等的数字类型。不幸的是,该代码现在要到 Boost-1.62 才会发布。

上面 (ab) 使用序列化支持的答案实际上非常酷而且非常聪明;) 但是,如果您想使用像 CityHash 这样的基于向量的散列器,它就行不通,我通过访问添加了一个使用它的示例肢体直接到文档: https : //htmlpreview.github.io/?https : //github.com/boostorg/multiprecision/blob/develop/doc/html/boost_multiprecision/tut/hash.html要么直接肢体访问或者序列化提示当然适用于所有以前的版本。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM