Définir l'opération en c ++ (mettre à jour la valeur existante)

21

Voici mon code:

 while (it!=s.end())  //here 's' is a set of stl and 'it' is iterator of set
    {   
        *it=*it-sub;    //'sub' is an int value
        it++;
    }

Je ne peux pas mettre à jour la valeur de set by iterator. Je veux soustraire une valeur entière «sub» de tous les éléments de l'ensemble.

Quelqu'un peut-il m'aider là où se trouve le problème réel et quelle serait la solution réelle?

Voici le message d'erreur:

error: assignment of read-only location it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~
Imtiaz Mehedi
la source
1
Veuillez mettre à niveau vers un exemple reproductible minimal .
Yunnosch
9
Les éléments de l'ensemble ne peuvent être lus que. En en modifiant un, vous réorganiseriez d'autres éléments dans l'ensemble.
rafix07
3
La solution est d'effacer l'itérateur et d'en insérer un nouveau avec la clé *it - sub. Veuillez noter que std::set::erase()renvoie un nouvel itérateur qui doit être utilisé dans votre cas pour que la whileboucle fonctionne correctement.
Scheff
2
@Scheff Pouvez-vous faire cela pendant que vous ierez sur un ensemble? Ne pourrait-il pas se retrouver dans une boucle sans fin? Surtout lorsque vous faites quelque chose de pertinent pour l'ensemble actuellement itéré qui place les éléments visités là où ils seront à nouveau visités?
Yunnosch
1
Imtiaz Par curiosité, dans le cas où il y aurait un suivi pour cette mission, pourriez-vous le rapporter ici dans un commentaire (je suppose que c'est une mission sans rien de mal, votre question est très bien)? Comme vous pouvez le voir dans mes commentaires sur la réponse de Scheff, je spécule sur le plan plus large des enseignants avec cela. Juste de la curiosité.
Yunnosch

Réponses:

22

Les valeurs clés des éléments dans a std::setsont constpour une bonne raison. Les modifier peut détruire l'ordre qui est essentiel pour a std::set.

Par conséquent, la solution consiste à effacer l'itérateur et à en insérer un nouveau avec la clé *it - sub. Veuillez noter que std::set::erase()renvoie un nouvel itérateur qui doit être utilisé dans votre cas pour que la boucle while fonctionne correctement.

#include<iostream>
#include<set>

template <typename T>
std::ostream& operator<<(std::ostream &out, const std::set<T> &values)
{
  const char *sep = "{ ";
  for (const T &value : values) { out << sep << value; sep = ", "; }
  return out << " }";
}

int main()
{
  std::set<int> test{ 11, 12, 13, 14, 15 };
  std::cout << "test: " << test << '\n';
  const int sub = 10;
  std::set<int>::iterator iter = test.begin();
  while (iter != test.end()) {
    const int value = *iter;
    iter = test.erase(iter);
    test.insert(value - sub);
  }
  std::cout << "test: " << test << '\n';
}

Production:

test: { 11, 12, 13, 14, 15 }
test: { 1, 2, 3, 4, 5 }

Démo en direct sur coliru


Les changements en cours d' std::setitération ne sont pas un problème en général mais peuvent provoquer des problèmes subtils.

Le fait le plus important est que tous les itérateurs utilisés doivent être conservés intacts ou ne plus être utilisés. (C'est pourquoi l'itérateur actuel de l'élément d'effacement est affecté avec la valeur de retour std::set::erase()qui est soit un itérateur intact ou la fin de l'ensemble.)

Bien sûr, des éléments peuvent également être insérés derrière l'itérateur actuel. Bien que ce ne soit pas un problème concernant le, std::setcela peut casser la boucle de mon exemple ci-dessus.

Pour le démontrer, j'ai un peu modifié l'exemple ci-dessus. Veuillez noter que j'ai ajouté un compteur supplémentaire pour accorder la fin de la boucle:

#include<iostream>
#include<set>

template <typename T>
std::ostream& operator<<(std::ostream &out, const std::set<T> &values)
{
  const char *sep = "{ ";
  for (const T &value : values) { out << sep << value; sep = ", "; }
  return out << " }";
}

int main()
{
  std::set<int> test{ 11, 12, 13, 14, 15 };
  std::cout << "test: " << test << '\n';
  const int add = 10;
  std::set<int>::iterator iter = test.begin();
  int n = 7;
  while (iter != test.end()) {
    if (n-- > 0) {
      const int value = *iter;
      iter = test.erase(iter);
      test.insert(value + add);
    } else ++iter;
  }
  std::cout << "test: " << test << '\n';
}

Production:

test: { 11, 12, 13, 14, 15 }
test: { 23, 24, 25, 31, 32 }

Démo en direct sur coliru

Scheff
la source
1
Est-il possible que cela ne fonctionne que pour soustraire quelque chose, mais pourrait se retrouver dans une boucle sans fin si l'opération provoque une réinsertion à un endroit où il sera visité plus tard ...?
Yunnosch
@Yunnosch Outre le danger de boucle infinie, il n'y a aucun problème à insérer des itérateurs derrière l'itérateur actuel. Les itérateurs sont stables dans le std::set. Il peut être nécessaire de considérer le cas frontière que le nouvel itérateur est inséré directement derrière l'effacé. - Il sera ignoré après insertion dans la boucle.
Scheff
3
En C ++ 17, vous pouvez extractnoeuds, modifier leurs clés et les retourner dans set. Il serait plus efficace, car il évite les allocations inutiles.
Daniel Langr
Pourriez-vous élaborer "Il sera ignoré après insertion dans la boucle.". Je pense que je ne comprends pas votre point de vue.
Yunnosch
2
Un autre problème est que la valeur d'un élément soustrait peut être la même que l'une des valeurs non encore traitées dans le std::set. Étant donné que vous ne pouvez pas avoir le même élément deux fois, l'insertion laissera simplement std::setinchangé et vous perdrez l'élément ultérieurement. Considérez par exemple l'ensemble d'entrée: {10, 20, 30}avec add = 10.
ComicSansMS
6

Simple à remplacer simplement par un autre ensemble

std::set<int> copy;

for (auto i : s)
    copy.insert(i - sub);

s.swap(copy);
acraig5075
la source
5

Vous ne pouvez pas muter des éléments de std::set par conception. Voir

https://en.cppreference.com/w/cpp/container/set/begin

Étant donné que l'itérateur et le const_iterator sont des itérateurs constants (et peuvent en fait être du même type), il n'est pas possible de muter les éléments du conteneur via un itérateur renvoyé par l'une de ces fonctions membres.

C'est parce que l'ensemble est trié . Si vous mutez un élément dans une collection triée, la collection doit être triée à nouveau, ce qui est bien sûr possible, mais pas la manière C ++.

Vos options sont:

  1. Utilisez un autre type de collection (non trié).
  2. Créez un nouvel ensemble et remplissez-le d'éléments modifiés.
  3. Supprimez un élément std::set, modifiez-le, puis réinsérez-le. (Ce n'est pas une bonne idée si vous souhaitez modifier chaque élément)
x00
la source
4

A std::setest généralement implémenté comme un arbre binaire à équilibrage automatique dans STL. *itest la valeur de l'élément qui est utilisé pour ordonner l'arbre. S'il était possible de le modifier, la commande deviendrait invalide donc il n'est pas possible de le faire.

Si vous souhaitez mettre à jour un élément, vous devez trouver cet élément dans l'ensemble, le supprimer et insérer la valeur mise à jour de l'élément. Mais puisque vous devez mettre à jour les valeurs de tous les éléments, vous devez alors effacer et insérer tous les éléments un par un.

Il est possible de le faire en une seule boucle fournie sub > 0. S.erase(pos)supprime l'itérateur en position poset renvoie la position suivante. Si sub > 0, la valeur mise à jour que vous insérerez viendra avant la valeur au nouvel itérateur dans l'arborescence mais si sub <= 0, alors la valeur mise à jour viendra après la valeur au nouvel itérateur dans l'arborescence et donc vous vous retrouverez dans un boucle infinie.

for (auto itr = S.begin(); itr != S.end(); )
{
    int val = *itr;
    itr = S.erase(itr);
    S.insert(val - sub);
}
lucieon
la source
C'est une bonne façon ... Je pense que c'est la seule façon de le faire. Il suffit de supprimer et d'insérer à nouveau.
Imtiaz Mehedi
3

L'erreur explique à peu près le problème

Les membres du std::setconteneur sont const. Les modifier annule leur commande respective.

Pour changer des éléments dans std::set , vous devrez effacer l'élément et le réinsérer après l'avoir changé.

Vous pouvez également utiliser std::mappour surmonter ce scénario.

P0W
la source