【问题标题】:c++ map find() to possibly insert(): how to optimize operations?c++ 将 find() 映射到可能的 insert():如何优化操作?
【发布时间】:2023-04-03 00:18:01
【问题描述】:

我正在使用 STL 地图数据结构,此时我的代码首先调用 find():如果该键以前不在地图中,它会调用 insert() 它,否则它什么也不做。

map<Foo*, string>::iterator it;
it = my_map.find(foo_obj);   // 1st lookup

if(it == my_map.end()){
  my_map[foo_obj] = "some value";  // 2nd lookup
}else{
  // ok do nothing.
}

我想知道是否有比这更好的方法,因为据我所知,在这种情况下,当我想插入一个还不存在的键时,我会在地图数据结构中执行 2 次查找:一次对于 find()insert() 中的一个(对应于 operator[] )。

提前感谢您的任何建议。

【问题讨论】:

    标签: c++ stl map


    【解决方案1】:

    通常,如果您执行查找和插入操作,那么您希望保留(并检索)旧值(如果它已经存在)。如果您只想覆盖任何旧值,map[foo_obj]="some value" 会这样做。

    以下是获取旧值或插入新值(如果它不存在)的方法,只需一次地图查找:

    typedef std::map<Foo*,std::string> M;
    typedef M::iterator I;
    std::pair<I,bool> const& r=my_map.insert(M::value_type(foo_obj,"some value"));
    if (r.second) { 
        // value was inserted; now my_map[foo_obj]="some value"
    } else {
        // value wasn't inserted because my_map[foo_obj] already existed.
        // note: the old value is available through r.first->second
        // and may not be "some value"
    }
    // in any case, r.first->second holds the current value of my_map[foo_obj]
    

    这是一个非常常见的习语,您可能想要使用辅助函数:

    template <class M,class Key>
    typename M::mapped_type &
    get_else_update(M &m,Key const& k,typename M::mapped_type const& v) {
        return m.insert(typename M::value_type(k,v)).first->second;
    }
    
    get_else_update(my_map,foo_obj,"some value");
    

    如果你有一个昂贵的 v 计算,如果它已经存在,你想跳过它(例如 memoization),你也可以概括它:

    template <class M,class Key,class F>
    typename M::mapped_type &
    get_else_compute(M &m,Key const& k,F f) {
       typedef typename M::mapped_type V;
       std::pair<typename M::iterator,bool> r=m.insert(typename M::value_type(k,V()));
       V &v=r.first->second;
       if (r.second)
          f(v);
       return v;
    }
    

    例如在哪里

    struct F {
      void operator()(std::string &val) const 
      { val=std::string("some value")+" that is expensive to compute"; }
    };
    get_else_compute(my_map,foo_obj,F());
    

    如果映射类型不可默认构造,则让 F 提供默认值,或向 get_else_compute 添加另一个参数。

    【讨论】:

    • 好的,所以这确实是唯一的方法,之前对 operator[] 的评论是错误的(如果键已经在地图中,它会覆盖之前的键/值对)
    【解决方案2】:

    有两种主要方法。第一种是使用 insert 函数,它接受一个值类型,返回一个迭代器和一个 bool,指示是否发生了插入,并将迭代器返回到具有相同键的现有元素或新插入的元素。

    map<Foo*, string>::iterator it;
    it = my_map.find(foo_obj);   // 1st lookup
    
    my_map.insert( map<Foo*, string>::value_type(foo_obj, "some_value") );
    

    这样做的好处是简单。主要缺点是无论是否需要插入,您总是为第二个参数构造一个新值。在字符串的情况下,这可能无关紧要。如果您的价值构建起来很昂贵,这可能会比必要的浪费更多。

    解决这个问题的一种方法是使用插入的“提示”版本。

    std::pair< map<foo*, string>::iterator, map<foo*, string>::iterator >
        range = my_map.equal_range(foo_obj);
    
    if (range.first == range.second)
    {
        if (range.first != my_map.begin())
            --range.first;
    
        my_map.insert(range.first, map<Foo*, string>::value_type(foo_obj, "some_value") );
    }
    

    只有在提供的迭代器之后立即插入元素时才保证插入g处于摊销常数时间,因此如果可能,--

    编辑

    如果这需要-- 看起来很奇怪,那么它就是。标准中有一个未解决的缺陷 (233) 突出了此问题,尽管在重复问题 246 中对该问题的描述更清楚,因为它适用于 map

    【讨论】:

    • “提示”版本不适用于 hash_map,但对于 map,这可能比为我的 get_else_compute 定义一个 lambda 更简单,我默认构造然后修改返回的迭代器的值(你可以内联当然没有 lambda)。
    • 通过 hash_map,我想我的意思是 tr1 或 C++0x 中的 std::unordered_map。
    • 没错,如果插入发生在提示之前,则不能保证提示在恒定时间内摊销,因为这将在容器的开头和结尾起作用。跨度>
    • Dinkumware std::map 确保“如果插入点紧跟在 where 之前或之后,则插入可以在摊销的常数时间内发生,而不是对数时间。”
    【解决方案3】:

    在您的示例中,您希望在未找到时插入。如果默认构造和之后设置的值并不昂贵,我建议使用 1 查找的更简单版本:

    string& r = my_map[foo_obj];    // only lookup & insert if not existed
    if (r == "") r = "some value";  // if default (obj wasn't in map), set value
                                    // else existed already, do nothing
    

    如果您的示例告诉您真正想要什么,请考虑将该值添加为 str Foo::s,因为您已经拥有该对象,因此不需要查找,只需检查它是否具有该成员的默认值。并将 objs 保存在 std::set 中。即使扩展 class FooWithValue2 也可能比使用 map 便宜。

    但是,如果确实需要像这样通过地图连接数据,或者如果您只想在数据存在时进行更新,那么 Jonathan 有答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-21
      • 2013-10-25
      • 1970-01-01
      • 2012-08-28
      • 1970-01-01
      • 2012-10-11
      • 2021-12-01
      • 2020-07-20
      相关资源
      最近更新 更多