web-dev-qa-db-fra.com

Comment mettre à jour un élément existant de std :: set?

J'ai un std::set<Foo>, et j'aimerais mettre à jour une valeur d'un élément existant. Notez que la valeur que je mets à jour ne change pas l'ordre dans l'ensemble:

#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;
}

Existe-t-il un moyen concis de le faire? Ou dois-je vérifier si l'élément est déjà là, et si oui, le supprimer, ajouter la valeur et réinsérer?

40
Frank

Puisque val n'est pas impliqué dans la comparaison, il pourrait être déclaré 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;
  }
};

Cela implique que la valeur de val peut changer dans un Foo à constance logique, ce qui signifie qu'elle ne devrait pas affecter les autres opérateurs de comparaison, etc.

Ou vous pouvez simplement supprimer et insérer, cela prend O(1) temps supplémentaire (par rapport à l'accès et à la modification) si l'insertion utilise la position juste avant juste après l'ancien comme indice.

Quelque chose comme:

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

N'essayez pas de résoudre ce problème en contournant la constance des éléments dans un set. Au lieu de cela, pourquoi ne pas utiliser map, qui exprime déjà la relation valeur-clé que vous modélisez et fournit des moyens simples de mettre à jour les éléments existants.

25
Mark B

Rendez val mutable comme:

mutable int val;

Vous pouvez maintenant changer/modifier/muter val même si foo est const:

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

Soit dit en passant, d'après votre code, vous semblez penser que si p.second est vrai, alors la valeur existait déjà dans l'ensemble, et donc vous mettez à jour la valeur associée. Je pense que vous vous êtes trompé. C'est en fait l'inverse. Le doc sur cpluscplus dit,

L'élément pair :: second de la paire est défini sur true si un nouvel élément a été inséré ou sur false si un élément avec la même valeur existait.

ce qui est correct, à mon avis.


Cependant, si vous utilisez std::map, votre solution serait simple:

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

Que fait ce code? m[value.first] crée une nouvelle entrée si la clé n'existe pas dans la carte, et la valeur de la nouvelle entrée est la valeur par défaut de int qui est zéro. Il ajoute donc value.second à zero. Ou bien si la clé existe, elle ajoute simplement value.second à elle. Autrement dit, le code ci-dessus est équivalent à ceci:

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
    }
}

Mais c'est trop, non? Ses performances ne sont pas bonnes. Le premier est plus concis et très performant.

5
Nawaz