web-dev-qa-db-ja.com

std :: setの既存の要素を更新する方法は?

私は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;
}

これを行う簡単な方法はありますか?または、要素が既に存在するかどうかを確認する必要がありますか?その場合、それを削除し、値を追加して再挿入しますか?

40
Frank

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で変化する可能性があることを意味します。つまり、他の比較演算子などに影響を与えるべきではありません。

または、挿入と位置を使用する場合は、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);
}
53
Cubbi

set内のアイテムの定数を回避することでこの問題を解決しようとしないでください。代わりに、モデリングしているキーと値の関係を既に表現し、既存の要素を簡単に更新できるmapを使用してください。

25
Mark B

valを次のように変更可能にします。

mutable int val;

valがconstである場合でも、fooを変更/変更/変更できます。

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

ちなみに、コードから、p.secondがtrueの場合、値は既にセットに存在しているため、関連する値を更新すると考えるようです。あなたはそれを間違えたと思う。実際、それは逆です。 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.secondzeroに追加します。または、キーが存在する場合は、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
    }
}

しかし、これは多すぎませんか?パフォーマンスは良くありません。以前のものは、より簡潔で非常に高性能です。

5
Nawaz