【问题标题】:How to use std::unordered_map with 'external' keys如何使用带有“外部”键的 std::unordered_map
【发布时间】:2021-04-23 16:37:21
【问题描述】:

我的目标是编写一个像 unordered_map 一样工作的类,但保持元素的插入顺序,同时仍然允许通过键进行 O(1) 查找。

我的做法如下:

// like unordered_map but keeps the order of the elements for iteration
// implemented as a vector with a unordered_map for constant time lookups
// consider if element removal is needed, since it is O(N) because of the use of a vector of elements
template <typename KEY, typename VAL>
struct ordered_map {
    struct KeyValue {
        KEY key;
        VAL value;
    };

    std::vector<KeyValue> elements; // KeyValue pairs in order of insertion to allow iteration
    std::unordered_map<KEY, int> indexmap; // key -> index map to allow O(1) lookup, is there a way to avoid the redundant key?
    
    //...
}

但是我有一个问题,在我的方法中,我想使用“外部”存储的键来查找索引映射(在基于映射中索引值的元素向量中)。

std::sort 例如允许传入一个比较器, 但 unordered_sap 似乎没有类似的东西。

我真的无法在网上找到有关如何完成此操作的任何信息,但我可能使用错误的术语进行搜索。

stl完全支持这种方法吗?

或者我需要两次存储密钥, 我想避免这种情况,因为键可以是堆对象,例如 std::strings。

编辑:unordered_map 代替 unordered_set 不起作用

【问题讨论】:

  • 我猜indexmap 持有密钥的哈希值,因为它使用的是std::size_t。那么使用外部存储的密钥有什么问题呢?您只需散列密钥并检查它是否存在于 indexmap?
  • 也许boost::multi_index 可以帮忙? Example
  • KeyValue 很小,因此同时拥有std::set&lt;KeyValue&gt;std::unordered_set&lt;KeyValue&gt; 应该不是问题。另一方面,我闻到了XY problem,所以请解释一下为什么你有这个要求。
  • 我可能会改变你的方法。使用 unordered_map 保存值,使用带有指针的 vector 来跟踪订单。
  • 如果您希望unordered_set&lt;size_t&gt; 实际上是unordered_map&lt;SomeKey, size_t&gt;,那么编写它就不会那么混乱了。然后SomeKey 可以只是std::reference_wrapper&lt;KEY&gt; - 尽管我同意将 KEY 保留在地图中并管理外部迭代器序列更明智。

标签: c++ stl unordered-map


【解决方案1】:

我的目标是编写一个像 unordered_map 一样工作的类,但保持元素的插入顺序,同时仍然允许 O(1) 按键查找

...并且复制密钥,以防它很大和/或昂贵。

所以,你想要两件事:

  1. 具有与std::unordered_map 相同语义的恒定时间关联查找(没有重复的键,否则您会/应该要求std::unordered_multimap
  2. 顺序索引跟踪插入顺序

您已选择使用顺序容器实现关联查找,并使用关联容器实现顺序索引。我不知道为什么,但让我们尝试更自然的选择:

  1. 关联查找应该只是std::unordered_map&lt;Key, SomeValueType&gt;
  2. 顺序索引可以是std::vector&lt;SomeValueType*&gt;

剩余的空白是SomeValueType:它可能只是VAL,但是每当我们删除某些东西时,我们都需要做额外的工作来修复顺序索引。我们可以改为std::pair&lt;VAL, size_t&gt;,这样它就可以存储我们需要在擦除时删除的迭代器的索引i。不利的一面是,除了将所有 i+1..N 向量元素向下移动一个之外,我们需要更新每个地图元素的索引值。

如果您想保留恒定时间擦除,您可能需要顺序索引为std::list&lt;SomeValueType*&gt;,并在您的地图元素中保留std::list::iterator。链表上的实际线性迭代会比向量慢,但在擦除元素时会得到相同的复杂度。


注意。顺序索引确实需要存储指针而不是迭代器——我最初忘记了无序映射的失效行为。但是,如果您想访问密钥,它们显然可以是 std::unordered_map&lt;key, SomeValueType&gt;::value_type 指针……我只是写出了较短的替代方案。

【讨论】:

  • 迭代器在重新散列时可能会失效,但指向该值的指针不会。
  • 很少需要擦除,但使用 std::list 的方法是一个很好的提示,谢谢。我倒退的原因可能是因为我认为哈希图是一种加速结构,而向量是我真正想要的“真实”数据结构。实际上,考虑到地图已经在内部使用了链表(我认为),更好的解决方案是简单地添加指向已经存在的元素的指针以跟踪顺序。
猜你喜欢
  • 2016-03-14
  • 1970-01-01
  • 2012-07-09
  • 1970-01-01
  • 1970-01-01
  • 2017-04-27
  • 1970-01-01
  • 2020-03-09
  • 2018-07-09
相关资源
最近更新 更多