std::类似映射的类型,键上没有compare/hash

std::map-like type without compare / hash on key

本文关键字:hash compare 映射 类型 std      更新时间:2023-10-16

我刚刚遇到一个小问题,想知道在众多解决方案中哪一个是最好/合适的。

我有一个std::map,它为每个键和值都有一个自定义类(所以交换它们不会解决任何问题)。

struct FooStruct
{
    bool testOne;
    bool testTwo;
};
struct BarStruct
{
    // some data
};
const std::map<FooStruct, BarStruct>
{
    { { true, false }, BarStruct(someValues) },
    { { false, true }, BarStruct(someOtherValues) }
};

所以现在,FooStruct不能以任何合理的方式"比较",BarStruct也不能。使用unordered_map也没有帮助,因为这需要一个哈希函数,我当然可以用很多方法实现它,但我怀疑这是否是获得真正未排序映射的最简单方法。

我也不关心性能,因为地图中只有4个元素。不过下次可能会有几千。

针对评论:这是一个抽象的例子。问题是,如果有几个测试,就可以很容易地比较结构中布尔测试结果的集合,但由于排列的数量随着n的增长而快速增长,我正在寻找一个可扩展的解决方案。

也许总体上有std::map类型的替代方案,例如std::pairstd::vector,但也有其他缺点。

如果你的结构FooStruct有很多测试结果,那么你有不同的结构,比如这样,测试的数量也不同,而你没有一个可扩展的解决方案。

使用bitset(ref)编写一个可扩展的版本。例如,您可以使用bitset::to_ulong(ref)进行比较(假设您的测试结果少于64个)。

struct FooStruct
{
    std::bitset<5> result; // can hold 5 results
    friend bool operator<(const FooStruct& a, const FooStruct& b) {
        return a.result.to_ulong() < b.result.to_ulong();
    }
};

否则,您必须手动聚合。例如:

struct FooStruct
{
    bool testOne;
    bool testTwo;
    bool testThree;
    unsigned long key() const {
        return testOne + (testTwo << 1) + (testThree << 2);
    }
    friend bool operator<(const FooStruct& a, const FooStruct& b) {
        return a.key() < b.key();
    }
};

比较是否"合理"并不重要,重要的是它实现了严格的弱排序。这很容易实现。

#include <tuple>
bool comp(const FooStruct& lhs, const FooStruct& rhs)
{
  return std::tie(lhs.testOne, lhs.testTwo) < 
         std::tie(rhs.testOne, rhs.testTwo);
}

至于unordered_map,它是一个哈希表,因此您需要提供一个哈希函数和一个相等性比较。没有办法绕过这一点。

另一个"可扩展"解决方案:

using FooStruct = std::vector<bool>;
std::map<FooStruct, BarStruct> foobarite
{
    { { true, false }, {} },
    { { false, true }, {} },
};

而且,如果你想在FooStruct中保留命名属性,还有另一个:

#include <unordered_map>
#include <functional>
#include <algorithm>
template <class T>
inline void hash_combine(std::size_t & seed, const T & v)
{
  std::hash<T> hasher;
  seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <typename Struct>
struct hash {
    inline std::size_t operator()(const Struct& obj ) const
    {
        const unsigned char* p = reinterpret_cast<const unsigned char*>( &obj );
        std::size_t seed = std::hash<unsigned char>{}(p[0]);
        for (unsigned int i = 1; i < sizeof(obj); ++i) {
            hash_combine(seed, p[i]);
        }
        return seed;
    }
};
template <typename Struct>
struct equal {
    bool operator()(const Struct& a, const Struct& b) const
    {
        const unsigned char* pa = reinterpret_cast<const unsigned char*>( &a );
        const unsigned char* pb = reinterpret_cast<const unsigned char*>( &b );
        return std::equal(pa, pa+sizeof(Struct), pb);
    }
};
struct FooStruct {
    bool testOne;
    bool testTwo;
};
std::unordered_map<FooStruct, BarStruct, hash<FooStruct>, equal<FooStruct>> foobarite
{
    { { true, false }, {} },
    { { false, true }, {} }
};