如果键在std :: map中不存在,如何从std :: unordered_set中有效删除键?

时间:2018-12-06 13:59:52

标签: c++ c++11

我有一个std::map和一个std::unordered_set,具有相同的密钥。

我要从集合中删除地图中不存在的所有键。

我的想法是执行以下操作:

#include <map>
#include <unordered_set>

int main()
{
    std::map<uint64_t, std::string> myMap = { 
                                                {1, "foo"},
                                                {2, "bar"},
                                                {3, "morefoo"},
                                                {4, "morebar"} 
                                            };

    std::unordered_set<uint64_t> mySet = { 1, 2, 3, 4, 123 };

    for (const auto key : mySet)
    {
        const auto mapIterator = myMap.find(key);

        if (myMap.end() == mapIterator)
            mySet.erase(key);
    }

    return 0;
}

然后每隔几秒钟在计时器回调中调用它,但是,当试图从123删除密钥mySet时,上面的代码段抛出一个断言,说明:

  

列表迭代器不可递增。

问题是,即使它没有引发异常,我也觉得这个想法远非优雅/最优。我想知道是否有更好的方法来解决这个问题?

谢谢。

2 个答案:

答案 0 :(得分:2)

如评论中所述

for (const auto key : mySet)
{
    const auto mapIterator = myMap.find(key);

    if (myMap.end() == mapIterator)
        mySet.erase(key);
}

将具有未定义的行为。从123中删除元素mySet时,会使基于范围的for循环使用的迭代器无效。执行完该操作后,不允许递增该迭代器。您可以做的是切换到常规的for循环,以便可以控制迭代器何时递增,如

for (auto it = mySet.begin(); it != mySet.end();)
{
    if (myMap.find(*it) == myMap.end())
        it = mySet.erase(it);
    else
        ++it;
}

现在您将始终拥有一个有效的迭代器,因为erase将返回下一个有效的迭代器。

答案 1 :(得分:2)

如对问题How to remove from a map while iterating it?的回答所述,在for范围循环中对其进行迭代时,您无法擦除容器中的元素,因此您的代码应使用迭代器。这样的答案已经提供了,我不会重复。

为了提高效率,您有一种非常错误的方法-您在std::map的同时进行std::unordered_set的查找,这与std::unorederd_set可以提供更快的查找时间相反(否则就没有意义了)因为您可以使用它代替std::set)。一种可能的方法是创建副本:

auto copySet = mySet;
for( const auto &p : myMap )
    copySet.erase( p.first );
for( const auto v : copySet )
    mySet.erase( v );

这可能会更有效,但这取决于您的数据。为容器选择合适的数据类型的更好方法。

注意:通过错误的方法,我的意思是仅针对您所提出的问题的这种效率,但这似乎是更大程序的一部分,并且此解决方案可能是正确的,因为在某些情况下,当此数据结构效果很好。