【问题标题】:std::map-like type without compare / hash on key没有比较/哈希键的 std::map-like 类型
【发布时间】:2014-04-13 15:47:51
【问题描述】:

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

我有一个 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 个元素。不过可能还有几千次。

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

也许总体上可以替代std::map 类型,例如std::vectorstd::pair 但这也有其他缺点。

【问题讨论】:

  • 八个元素?最好使用 array 或硬编码。请记住,O(log N) 的东西适用于 N 的 large 值;对于非常小的值,许多优化结构(如红黑树)不如更简单的解决方案。
  • 八个元素?似乎只有 4 个关键值。你从哪里得到其他 4 个?
  • 我喜欢那些 cmets 和随机的否决票......正如我已经说过的 - 是的 - 有很多方法可以避免这个问题,但我很好奇并想学习......我或某人else 现在或将来可能会遇到 1000 个元素的问题,所以问有什么问题?
  • @Deduplicator 对不起我的错误,我简化了这个问题的例子。
  • 真的不清楚“问题”是什么。如果你想要std::map,实现严格的弱排序比较。如果你想要unordered_map,实现哈希和相等比较。

标签: c++ c++11 stl containers stdmap


【解决方案1】:

如果您的结构 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();
    }
};

【讨论】:

  • @Danvil 好主意!但是你的回答给了我一个真正可扩展的解决方案的想法。作为它的 c++,不能只使用内存布局来创建哈希吗?
  • @Xaser 你有没有看到我编辑的答案,我在其中使用内存布局来创建哈希?
【解决方案2】:

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

#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是哈希表,所以需要提供哈希函数和相等比较。没有办法解决这个问题。

【讨论】:

  • 是的,该解决方案适用于我的抽象示例,但我认为它不可扩展。就我而言,我有一个包含 12 个测试结果的结构,其他人可能有更多。我知道在这一点上,人们也许应该考虑另一种存储测试结果的方式,但是..
【解决方案3】:

另一个“可扩展”的解决方案:

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

【讨论】:

  • 这看起来也不错,但是我认为首先要实现一个结构来存储测试结果的好处是您可以命名每个测试结果。但也可能会费心说出几百个测试结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
  • 1970-01-01
  • 2015-10-26
  • 2013-10-17
  • 2012-05-27
  • 1970-01-01
相关资源
最近更新 更多