【问题标题】:How to update an existing element of std::set?如何更新 std::set 的现有元素?
【发布时间】:2011-11-12 12:23:41
【问题描述】:

我有一个std::set<Foo>,我想更新一些值 其中的现有元素。请注意,我正在更新的值不会更改集合中的顺序:

#include <iostream>
#include <set>
#include <utility>

struct Foo {
  Foo(int i, int j) : id(i), val(j) {}
  int id;
  int val;
  bool operator<(const Foo& other) const {
    return id < other.id;
  }
};

typedef std::set<Foo> Set;

void update(Set& s, Foo f) {
  std::pair<Set::iterator, bool> p = s.insert(f);
  bool alreadyThere = p.second;
  if (alreadyThere)
    p.first->val += f.val; // error: assignment of data-member
                           // ‘Foo::val’ in read-only structure
}

int main(int argc, char** argv){
  Set s;
  update(s, Foo(1, 10));
  update(s, Foo(1, 5));
  // Now there should be one Foo object with val==15 in the set.                                                                
  return 0;
}

有什么简洁的方法可以做到这一点吗?还是我必须检查元素是否已经存在,如果存在,请将其删除,添加值并重新插入?

【问题讨论】:

标签: c++ stl set


【解决方案1】:

由于val不参与比较,所以可以声明为mutable

struct Foo {
  Foo(int i, int j) : id(i), val(j) {}
  int id;
  mutable int val;
  bool operator<(const Foo& other) const {
    return id < other.id;
  }
};

这意味着val 的值可能会在逻辑上为常量 Foo 中发生变化,这意味着它不应影响其他比较运算符等。

或者您可以删除和插入,如果插入使用位置 just before 就在旧位置之后作为提示,则需要 O(1) 额外的时间(与访问和修改相比)。

类似:

bool alreadyThere = !p.second; // you forgot the !
if (alreadyThere)
{
    Set::iterator hint = p.first;
    hint++;
    s.erase(p.first);
    s.insert(hint, f);
}

【讨论】:

  • 根据 SGI STL 的实现,Amortized O(1) 插入是使用插入点之后 的迭代器调用的。您需要在提示之后使用元素,这样会更容易,因为如果您指向一个元素,它不能是 end()
  • @Dave S:是的,C++ 库要求说的是同样的事情,只是编辑过。
  • 这很有趣:根据我的 C++03 和 C++11 标准草案,提示的含义在两个版本之间发生了变化:在 C++03 中,表达式 @987654328 @(其中a 是关联容器,p 是提示迭代器,t 是要插入的值)必须具有复杂度 logarithmic in general, but amortized constant if t is inserted right *after* p。最后一句在 C++11 中进行了修改,现在声明复杂度应为amortized constant if t is inserted right *before* p。 (参见表 69 (C++03) 和表 102 (C++11))。
  • 我刚找到these comments,这很好地解释了为什么之前的版本是错误的。
  • 如果你看到像std&lt; { k; mutable v; } &gt; 这样的模式,你肯定必须改用map&lt; k, v &gt;mutable 是肮脏的解决方法。
【解决方案2】:

不要试图通过解决set 中项目的常量性来解决这个问题。相反,为什么不使用map,它已经表达了您正在建模的键值关系,并提供了更新现有元素的简单方法。

【讨论】:

  • 没错,为正确的问题选择正确的容器。
  • 我偶尔会遇到这个设计问题,但仍然没有找到好的解决方案。如果我的对象很简单,那么我可以使用地图来管理对象。假设我的对象使用 5 个属性进行排序,我可能必须将它们复制到元组中作为键。在某些情况下,可变策略非常糟糕。如果有人有好的想法,请在此处发布。
【解决方案3】:

使val 可变为:

mutable int val;

现在即使foo 是常量,您也可以更改/修改/变异val

void f(const Foo & foo)
{
     foo.val = 10;  //ok
     foo.id  = 11;  //compilation error - id is not mutable.
}

顺便说一句,从您的代码中,您似乎认为如果p.second 为真,那么该值已经存在于集合中,因此您更新了关联的值。我想,你搞错了。事实上,情况恰恰相反。 cpluscplus 的doc 说,

如果插入了新元素,则该对中的 pair::second 元素设置为 true;如果存在具有相同值的元素,则设置为 false。

我认为这是正确的。


但是,如果您使用std::map,您的解决方案将很简单:

void update(std::map<int,int> & m, std::pair<int,int> value) 
{
    m[value.first] += value.second;
}

这段代码有什么作用? m[value.first] 如果映射中不存在该键,则创建一个新条目,并且新条目的值是默认值 int 为零。所以它将value.second 添加到zero。否则,如果密钥存在,那么它只是将value.second 添加到它。也就是上面的代码等价于:

void update(std::map<int,int> & m, std::pair<int,int> value) 
{
    std::map<int,int>::iterator it = m.find(value);
    if ( it != m.end()) //found or not?
           it.second += value; //add if found
    else
    {
           m.insert(value); //insert if not found
    }
}

但这太过分了,不是吗?它的性能并不好。前一个更简洁,性能也很好。

【讨论】:

    【解决方案4】:

    如果您知道自己在做什么(集合元素本身不是 const 并且您没有更改比较中涉及的成员),那么您可以抛弃 const-ness:

    void update(Set& s, Foo f) {
      std::pair<Set::iterator, bool> p = s.insert(f);
      bool alreadyThere = !p.second;
      if (alreadyThere)
      {
        Foo & item = const_cast<Foo&>(*p.first);
        item.val += f.val;
      }
    }
    

    【讨论】:

      【解决方案5】:

      如果你有 KEY ,你可以使用 MAP 女巫可以非常快速地访问你的元素。在这种情况下,我认为使用 MAP 将是实现最快速度的更好方法。标准::地图

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-22
        • 2011-01-14
        • 2012-05-08
        • 1970-01-01
        相关资源
        最近更新 更多