删除所有未找到的内容,即删除未在set中找到的地图中的所有键/值

时间:2015-10-01 09:15:58

标签: c++ c++11 stl-algorithm

我尝试但未能使用std::algorithms进行以下操作: 我有一个std::map<key_t,value_t> cache和一个std::set<key_t> selected_items我希望从cache中删除键/值对,但selected_items中包含的键除外。

这是我在没有算法的情况下写的:

//This could really be written better with std::algorithms but time...
//Delete old
for (auto pair = cache.begin(); pair != cache.end(); ) {
    if (selected_items.find(pair->first) == selected_items.end())
        pair = cache.erase(pair);
    else
        ++pair;
}

为了利用算法库,我想我需要将std::set_difference与比较函数std::removestd::map::erase一起使用。但是我无法连接这些碎片,但在以下方面失败了:

  1. 正确的比较功能是什么?
  2. 我是否必须使用应删除的键生成临时集,还是可以直接使用输出迭代器进行删除/擦除?
  3. 我的代码应如何显示?

4 个答案:

答案 0 :(得分:5)

这实际上是一个非常有趣的问题!事实证明,涉及到一些困难......

  • std::map使用std::pair<const Key, T>,无法复制/移动std::pairs(请注意const
  • 没有算法可以执行对std::map<>::erase()的实际调用,因为它会使当前迭代器无效
  • 重新排序cache中元素的标准方法(例如简单调用std::partition),然后删除cache 中的最后一个元素 因第1点而工作

因此,您有两种可能性:

  • 构建自己的循环,适当地调用erase
  • 使用<algorithm>和存储结果的第二张地图

由于您只对第二种选择感兴趣,我们可以检查,例如使用std::set_difference()确实完全符合你的要求 但是,因为std::mapstd::set的迭代器指向不同类型的对象(std::pairKey),我们必须要小心我们的Comparator 一种天真的方法就是提供一个需要const std::pair &const Key &的函数。但是这个在我的机器上不起作用!(我不知道这是不是一个bug ... Mac OS X 10.10.5)因为std::set_difference()有时会调用{{1}参数反向排列......

长话短说,这是一个以ComparatorSFINAE

为特色的解决方案
std::set_difference()

答案 1 :(得分:1)

如果两个容器在key_t上使用相同的排序顺序,则只需走两个容器并删除一个容器中的元素(如果它不在另一个容器中而不必搜索它)。 O(N)复杂性。

不幸的是,没有一种标准算法可以为你做删除,因为它们适用于迭代器。要使用迭代器删除,需要使用其容器对象。

答案 2 :(得分:1)

天真的尝试:

#include <iostream>
#include <map>
#include <set>

int main() {
    std::map<int, int> m = {{1, 2}, {3, 4}, {4, 5}};
    std::set<int> s = {1, 3, 5};
    for (auto it = begin(m); it != end(m); ){
        if (s.count(it->first))
            m.erase(it++);
        else
            ++it;
    }
    for (auto &e : m){
        std::cout << e.first << ' ' << e.second << '\n';
    }
}

纳入@MaximEgorushkin的想法:

#include <iostream>
#include <map>
#include <set>

void erase_elements_from_map_that_are_not_in_set(
    std::map<int, int> &m, std::set<int> &s){   
    auto sit = begin(s);
    for (auto it = begin(m); it != end(m) && sit != end(s); ){
        while (*sit < it->first){
            ++sit;
            if (sit == end(s))
                return;
        }
        if (*sit == it->first)
            m.erase(it++);
        else
            ++it;
    }
}
int main() {
    std::map<int, int> m = {{1, 2}, {3, 4}, {4, 5}};
    std::set<int> s = {1, 3, 5};
    erase_elements_from_map_that_are_not_in_set(m, s);
    for (auto &e : m){
        std::cout << e.first << ' ' << e.second << '\n';
    }
}

您可能需要使用<while的常用比较功能替换s循环中的m

关于STL算法,您可以在第一个示例中使用std::find而不是count,但这只是尴尬。我不知道在这里使用STL算法的任何其他方式,我认为不需要它们。如果您经常需要,可以将代码放在自己的erase_elements_from_map_that_are_not_in_set函数中。

答案 3 :(得分:1)

这听起来像是erase-remove idiom

的情况
typedef std::map<int,std::string> cache_t;
typedef std::set<cache_t::key_type> set_t;

void update_cache(cache_t& cache, const set_t& selected_items)
{
    auto test = [selected_items](const cache_t::value_type& x){
        return selected_items.find(x.first) == selected_items.end();
    };
    cache.erase(std::remove_if(cache.begin(), cache.end(), test), cache.end());
}

但这里不可能,因为错误信息表明:

32883794.cc:16:64:   required from here
/usr/include/c++/4.8/bits/stl_pair.h:170:8: error: assignment of read-only member ‘std::pair<const int, std::basic_string<char> >::first’
  first = std::forward<first_type>(__p.first);

问题是我们只从iterator获得pair<const int key_t, value_t> map,因此无法移动其元素。

应该可以使用std::copy_if来创建cache的新实例,但与使用循环的方法相比,这可能会产生大量内存开销。