如何在C++中的Map或Set中修改Key值

我们对map中的修改value操作可以是如下的:

auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
myMap.find("two")->second = 22;

但是如果想修改key操作,则下面这段代码是错误的:

auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
myMap.find("two")->first = "dos";

如果是map<int, int>,则会报这样的错误:

error: assignment of read-only member ‘std::pair<const int, int>::first’
m.find(1)->first=3;

修改std::map的key值的问题

std::vectorstd::mapstd::set这些序列容器中,需要提供两个保证:

  • 元素应该以有序的顺序来存储
  • 确保元素是唯一的

但是在map的情况下,排序顺序很方便,以便找到与对数复杂度的键关联的值。为了保持这些不变性,容器std::map和std::set需要对它们的值在集合内的相对位置进行一些控制。

如果只是使用迭代器来修改值,例如之前使用的示例,则不会通知该容器。 这将使其结构不一致并破坏不变性。所以是read-only member

C++17中的key值修改

修改key的策略是通过容器携带的接口,而不是通过迭代器进行修改

c++17中给关联容器提供了一个方法:extract,这提供了保存容器元素的节点:

auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
 
auto const node = myMap.extract("two");

extract对容器具有修改作用:映射不再包含节点。 如果我们在调用提取之前和之后检查大小:

auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
 
std::cout << myMap.size() << '\n'; // 3
auto node = myMap.extract("two");
std::cout << myMap.size() << '\n'; // 2

所以此时node并不是和map中的元素相关联,所以不会破坏map的结构,修改key值就可以:

node.key() = "dos";

而这个node并没有提供value的修改方法,因为在map中直接操作会更加方便。

修改key的值后,需要将node重新插回到map结构中:

myMap.insert(std::move(node));

这里必须写上std::move,因为node只有移动构造,并没有拷贝构造函数。

当请求的node并不存在的时候

auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
 
auto node = myMap.extract("four");

node仍然是个有效的对象,我们可以将其用insert插入,但是不能访问它的key();所以最有效的措施应该是在extract之前判断一下是否为空:

auto myMap = std::map<std::string, int>{ {"one", 1}, {"two", 2}, {"three", 3} };
 
auto node = myMap.extract("two");
if (!node.empty())
{
    node.key() = "dos";
    myMap.insert(std::move(node));
}

C++17之前怎么做

在C++17之前,做法就是erase key所在的元素然后再更新后插入,但是这样的做法非常的低效而且不直接。

封装node

修改一个map的key值,比提取一个node、修改相应的值、最后插回map中这种表达会更清晰,虽然后者是前者的实现思想。所以我们可以将后者进行封装,抽象为前者的表达:

template<typename Container>
void replaceKey(Container& container,
                const typename Container::key_type& oldKey,
                const typename Container::key_type& newKey)
{
    auto node = container.extract(oldKey);
    if(!node.empty())
    {
        node.key() = newKey;
        container.insert(std::move(node));
    }
}
posted @ 2020-09-06 00:03  MrYun  阅读(13570)  评论(0编辑  收藏  举报