1

异构查找意味着我们可以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的保存字符串视图的内容。

Astd::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>>产生相同的结果,因此不应该有太多的障碍来完成这项工作。

4

1 回答 1

1

您可以通过定义类型来覆盖散列和比较来实现 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(), a.end(), b.begin(), b.end());
    }
    bool operator()(const std::vector<int>& a, const std::vector<int>& b) const {
        return std::equal(a.begin(), a.end(), b.begin(), b.end());
    }
    bool operator()(absl::Span<int> b, absl::Span<int> a) const {
        return std::equal(a.begin(), a.end(), b.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>没有转换运算符 to std::vector<int>,因此操作失败。

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

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

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


在评论中,您询问是否可以实现一个operator []覆盖,如果地图条目存在,则不复制向量,同时仍然只做一个哈希。这有点 hacky 并且仍然可能进行额外的比较,但我认为您可以使用存储密钥和已计算哈希的辅助类型来完成此操作:

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>, int, VectorHash, VectorEq> {
    using absl::flat_hash_map<std::vector<int>, int, VectorHash, VectorEq>::flat_hash_map;
    using absl::flat_hash_map<std::vector<int>, int, VectorHash, 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

于 2020-11-04T09:29:51.037 回答