在std::map中修改元素键的最快方法是什么?

What is the fastest way to change a key of an element inside std::map

本文关键字:方法 是什么 元素 map std 修改      更新时间:2023-10-16

我理解为什么不能这样做(平衡和东西):

iterator i = m.find(33);
if (i != m.end())
  i->first = 22;

但是到目前为止,改变键的唯一方法(我知道)是从树中删除节点,然后用不同的键插入值:

iterator i = m.find(33);
if (i != m.end())
{
  value = i->second;
  m.erase(i);
  m[22] = value;
}

这对我来说似乎效率很低,原因更多:

  1. 遍历树三次(+ balance)而不是两次(+ balance)

  2. 又一个不必要的值

    拷贝
  3. 不必要的释放,然后重新分配树内的节点

我发现分配和再分配是这三者中最差的。是我遗漏了什么,还是有更有效的方法?

我认为,在理论上,这应该是可能的,所以我不认为改变一个不同的数据结构是合理的。下面是我想到的伪算法:

  1. 在树中找到我想要更改密钥的节点

  2. 将其从树中分离(不要deallocate)

  3. 平衡
  4. 更改分离节点内的密钥

  5. 将节点插入树

  6. 平衡

在c++ 17中,新的map::extract函数允许您更改密钥。
例子:

std::map<int, std::string> m{ {10, "potato"}, {1, "banana"} };
auto nodeHandler = m.extract(10);
nodeHandler.key() = 2;
m.insert(std::move(nodeHandler)); // { { 1, "banana" }, { 2, "potato" } }

可以省略value;

const int oldKey = 33;
const int newKey = 22;
const iterator it = m.find(oldKey);
if (it != m.end()) {
  // Swap value from oldKey to newKey, note that a default constructed value 
  // is created by operator[] if 'm' does not contain newKey.
  std::swap(m[newKey], it->second);
  // Erase old key-value from map
  m.erase(it);
}

我在18个月前就提出了你的关联容器算法:

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html # 839

查找标记为:[2009-09-19 Howard add:]的注释。

当时,我们太接近FDIS而没有考虑这一变化。然而,我认为它非常有用(你显然同意),我想把它纳入TR2。也许你可以找到并通知你的c++国家机构代表,这是一个你希望看到的特性。

这还不确定,但我认为我们很有可能在c++ 17中看到这个特性!: -)

要求STL映射中的键是不可变的。

似乎如果你在配对的关键端有那么大的波动性,那么不同的数据结构或结构可能会更有意义。

你不能。

如你所见,这是不可能的。通过组织映射,可以有效地更改与键相关联的值,但不能相反。

你看一下Boost。MultiIndex,特别是它的模拟标准容器部分。提振。

您应该将分配留给分配器。: -)

正如你所说,当键改变时,可能会有很多重新平衡。这就是树的工作方式。也许22是树中的第一个节点,33是最后一个节点?我们知道什么?

如果避免分配很重要,也许你应该尝试vector或deque?它们以更大的块进行分配,因此可以节省对分配器的调用次数,但可能会浪费内存。所有容器都有各自的优缺点,由您决定在每种情况下哪种容器具有您需要的主要优势(假设这很重要)。

对于喜欢冒险的人:
如果您确信更改键不会影响顺序并且您永远不会出错,那么const_cast 让您无论如何都可以更改键。

如果您知道新键对映射位置是有效的(更改它不会改变顺序),并且您不想要将项目删除并添加到映射中的额外工作,您可以使用const_cast来更改键,如下面的unsafeUpdateMapKeyInPlace:

template <typename K, typename V, typename It>
bool isMapPositionValidForKey (const std::map<K, V>& m, It it, K key)
{
    if (it != m.begin() && std::prev (it)->first >= key)
        return false;
    ++it;
    return it == m.end() || it->first > key;
}
// Only for use when the key update doesn't change the map ordering
// (it is still greater than the previous key and lower than the next key).
template <typename K, typename V>
void unsafeUpdateMapKeyInPlace (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
{
    assert (isMapPositionValidForKey (m, it, newKey));
    const_cast<K&> (it->first) = newKey;
}

如果你想要一个解决方案,只在适当的时候改变,否则改变映射结构:

template <typename K, typename V>
void updateMapKey (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
{
    if (isMapPositionValidForKey (m, it, newKey))
    {
        unsafeUpdateMapKeyInPlace (m, it, newKey);
        return;
    }
    auto next = std::next (it);
    auto node = m.extract (it);
    node.key() = newKey;
    m.insert (next, std::move (node));
}