在C ++中实现跨度的异构查找

问题描述

异构查找意味着我们可以使用另一个有意义的兼容类型(例如std::string)来索引包含类型absl::string_view的键的哈希映射。例如,以下代码有效(出于兼容性原因,我在代码中使用Abseil库而不是C ++ 20):

std::string word = "bird";
absl::flat_hash_map<std::string,int> word_map;
word_map[word] = 1;
std::cout << word_map[absl::string_view(word)] << std::endl;

这是可行的(并且确实是可行的),因为我们处理哈希表所需要做的就是计算哈希函数的能力以及进行相等性比较的能力。因此,使用此方法读取哈希表应该很简单,写表也很有意义,因为哈希表可以创建一个新的std::string来保存字符串视图的内容

std::vector<T>也具有字符串视图absl::Span<T>类型的轻量级类似物。但是,相应的查找无效:​​

std::vector<int> nums = {1,2,3,4};
absl::flat_hash_map<std::vector<int>,int> int_map;
int_map[nums] = 1;
std::cout << int_map[absl::Span<int>(nums)] << std::endl;

编译器在最后一行抱怨operator[]不匹配。

问题:我如何实现这种异构查找,以便它以与字符串和字符串视图相同的方式适用于向量和跨度?

我可以看到absl::Hash<std::vector<int>>absl::Hash<absl::Span<int>>产生了相同的结果,因此进行这项工作应该不会有太多障碍。

解决方法

您可以通过定义覆盖哈希和比较的类型来实现Abseil的异构查询功能。根据文档,必须将其标记为is_transparent特性以支持转换。

struct VectorHash {
    using is_transparent = void;

    size_t operator()(absl::Span<int> v) const {
        return absl::Hash<absl::Span<const int>>{}(v);
    }
    size_t operator()(const std::vector<int>& v) const {
        return absl::Hash<absl::Span<const int>>{}(absl::Span<const int>{ v.data(),v.size() });
    }
};

struct VectorEq {
    using is_transparent = void;

    bool operator()(const std::vector<int>& a,absl::Span<int> b) const {
        return std::equal(a.begin(),a.end(),b.begin(),b.end());
    }
    bool operator()(absl::Span<int> b,const std::vector<int>& a) const {
        return std::equal(a.begin(),b.end());
    }
    bool operator()(const std::vector<int>& a,const std::vector<int>& b) const {
        return std::equal(a.begin(),absl::Span<int> a) const {
        return std::equal(a.begin(),b.end());
    }
};

using int_map_t = absl::flat_hash_map<std::vector<int>,int,VectorHash,VectorEq>;

这将使使用atfind的查找有效。但是[]仍然会失败。为什么?由于[]运算符是upsert-如果密钥不存在,它将创建密钥。 absl::string_view具有到std::string的显式转换运算符,因此,可以从一个作品中创建一个新的std::string密钥。 absl::Span<int>没有std::vector<int>的转换运算符,因此操作失败。

如果不是选择使用at而不是[],您仍然可以扩展类型:

struct int_map_t : absl::flat_hash_map<std::vector<int>,VectorEq> {
    using absl::flat_hash_map<std::vector<int>,VectorEq>::flat_hash_map;
    using absl::flat_hash_map<std::vector<int>,VectorEq>::operator [];
    int& operator [](absl::Span<int> v) {
        return operator [](std::vector<int> { v.begin(),v.end() });
    }
};

演示:https://godbolt.org/z/dW4av7


在注释中,您询问是否有可能实现operator []覆盖,如果地图条目存在,则该覆盖不复制矢量,而仍然仅进行一次哈希处理。这有点棘手,但仍然可能需要进行额外的比较,但是我认为您可以使用存储密钥和已经计算出的哈希值的辅助类型来完成此操作:

struct VectorHashMemo {
    size_t hash;
    absl::Span<int> key;

    explicit operator std::vector<int>() const {
        return { key.begin(),key.end() };
    }
};

struct VectorHash {
    /* ...existing overloads... */
    size_t operator()(VectorHashMemo v) const {
        return v.hash;
    }
};

struct VectorEq {
    /* ...existing overloads... */

    bool operator()(const std::vector<int>& a,VectorHashMemo b) const {
        return operator()(a,b.key);
    }
    bool operator()(VectorHashMemo a,const std::vector<int>& b) const {
        return operator()(a.key,b);
    }
    bool operator()(VectorHashMemo b,VectorHashMemo a) const {
        return operator()(a.key,b.key);
    }
};

然后,您只能显式地计算一次哈希,而两次访问该图:

struct int_map_t : absl::flat_hash_map<std::vector<int>,VectorEq>::operator [];
    int& operator [](absl::Span<int> v) {
        VectorHashMemo hash = { absl::Hash<absl::Span<int>>{}(v),v };
        auto it = find(hash);
        if (it != end()) {
            return it->second;
        } else {
            // calls the explicit conversion operator
            return operator [](hash);
        }
        return operator [](std::vector<int> { v.begin(),v.end() });
    }
};

演示:https://godbolt.org/z/fecevE