Operation in c ++ einstellen (vorhandenen Wert aktualisieren)

21

Hier ist mein 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++;
    }

Ich kann den Wert von set by iterator nicht aktualisieren. Ich möchte einen ganzzahligen Wert 'sub' von allen Elementen der Menge subtrahieren.

Kann mir jemand helfen, wo das eigentliche Problem liegt und was die eigentliche Lösung wäre?

Hier ist die Fehlermeldung:

error: assignment of read-only location it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~
Imtiaz Mehedi
quelle
1
Bitte aktualisieren Sie auf ein minimal reproduzierbares Beispiel .
Yunnosch
9
Elemente im Set können nur gelesen werden. Wenn Sie eines ändern, ordnen Sie andere Elemente im Satz neu an.
rafix07
3
Die Lösung besteht darin, den Iterator zu löschen und einen neuen mit Schlüssel einzufügen *it - sub. Bitte beachten Sie, dass std::set::erase()ein neuer Iterator zurückgegeben wird, der in Ihrem Fall verwendet werden muss, damit die whileSchleife ordnungsgemäß funktioniert.
Scheff
2
@Scheff Kannst du das tun, während du über ein Set fliegst? Könnte es nicht in einer Endlosschleife enden? Vor allem, wenn Sie etwas tun, das für die aktuell iterierte Menge relevant ist und besuchte Elemente dort platziert, wo sie erneut besucht werden?
Yunnosch
1
Imtiaz Könnten Sie aus Neugier, falls es ein Follow-up für diese Aufgabe gibt, dies hier in einem Kommentar melden (ich nehme an, dies ist eine Aufgabe, ohne etwas Schlechtes zu bedeuten, Ihre Frage ist in Ordnung)? Wie Sie in meinen Kommentaren zur Scheff-Antwort sehen können, spekuliere ich damit über den größeren Plan des Lehrers. Nur Neugier.
Yunnosch

Antworten:

22

Schlüsselwerte von Elementen in a std::sethaben consteinen guten Grund. Wenn Sie sie ändern, kann dies die Reihenfolge zerstören, die für a std::set.

Daher besteht die Lösung darin, den Iterator zu löschen und einen neuen mit Schlüssel einzufügen *it - sub. Bitte beachten Sie, dass std::set::erase()ein neuer Iterator zurückgegeben wird, der in Ihrem Fall verwendet werden muss, damit die while-Schleife ordnungsgemäß funktioniert.

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

Ausgabe:

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

Live-Demo auf coliru


Änderungen std::setwährend der Iteration sind im Allgemeinen kein Problem, können jedoch subtile Probleme verursachen.

Die wichtigste Tatsache ist, dass alle verwendeten Iteratoren intakt bleiben müssen oder nicht mehr verwendet werden dürfen. (Aus diesem Grund wird dem aktuellen Iterator des Löschelements der Rückgabewert zugewiesen, dessen Rückgabewert std::set::erase()entweder ein intakter Iterator oder das Ende der Menge ist.)

Natürlich können auch Elemente hinter dem aktuellen Iterator eingefügt werden. Dies ist zwar kein Problem, std::setkann aber die Schleife meines obigen Beispiels durchbrechen.

Um dies zu demonstrieren, habe ich das obige Beispiel ein wenig geändert. Bitte beachten Sie, dass ich einen zusätzlichen Zähler hinzugefügt habe, um die Beendigung der Schleife zu gewähren:

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

Ausgabe:

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

Live-Demo auf coliru

Scheff
quelle
1
Ist es möglich, dass dies nur zum Subtrahieren von etwas funktioniert, aber in einer Endlosschleife enden kann, wenn die Operation das Wiedereinsetzen an einem Ort verursacht, an dem es später besucht wird ...?
Yunnosch
@Yunnosch Neben der Gefahr einer Endlosschleife ist es kein Problem, Iteratoren hinter den aktuellen Iterator einzufügen. Iteratoren sind in der stabil std::set. Es kann erforderlich sein, den Grenzfall zu berücksichtigen, in dem der neue Iterator direkt hinter dem gelöschten eingefügt wird. - Es wird nach dem Einfügen in die Schleife übersprungen.
Scheff
3
In C ++ 17 können Sie extractKnoten erstellen, ihre Schlüssel ändern und sie wieder in set zurücksetzen. Dies wäre effizienter, da unnötige Zuweisungen vermieden werden.
Daniel Langr
Könnten Sie näher erläutern: "Es wird nach dem Einfügen in die Schleife übersprungen." Ich glaube, ich verstehe Ihren Standpunkt dort nicht.
Yunnosch
2
Ein weiteres Problem besteht darin, dass der Wert eines subtrahierten Elements möglicherweise mit einem der noch nicht verarbeiteten Werte in der identisch ist std::set. Da Sie nicht zweimal dasselbe Element haben können, bleibt beim Einfügen std::setdas Element unverändert, und Sie verlieren das Element später. Betrachten Sie zum Beispiel den Eingabesatz: {10, 20, 30}mit add = 10.
ComicSansMS
6

Einfach durch ein anderes Set zu ersetzen

std::set<int> copy;

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

s.swap(copy);
acraig5075
quelle
5

Sie können Elemente std::setvon Design nicht mutieren . Sehen

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

Da sowohl Iterator als auch const_iterator konstante Iteratoren sind (und tatsächlich vom selben Typ sein können), ist es nicht möglich, die Elemente des Containers durch einen Iterator zu mutieren, der von einer dieser Elementfunktionen zurückgegeben wird.

Das liegt daran, dass set sortiert ist . Wenn Sie ein Element in einer sortierten Sammlung mutieren, muss die Sammlung erneut sortiert werden, was natürlich möglich ist, aber nicht auf C ++ - Weise.

Ihre Optionen sind:

  1. Verwenden Sie einen anderen Sammlungstyp (unsortiert).
  2. Erstellen Sie einen neuen Satz und füllen Sie ihn mit geänderten Elementen.
  3. Entfernen Sie ein Element aus std::set, ändern Sie es und fügen Sie es erneut ein. (Es ist keine gute Idee, wenn Sie jedes Element ändern möchten.)
x00
quelle
4

A std::setwird normalerweise als selbstausgleichender Binärbaum in STL implementiert. *itist der Wert des Elements, mit dem der Baum geordnet wird. Wenn es möglich wäre, es zu ändern, würde die Bestellung ungültig werden, daher ist dies nicht möglich.

Wenn Sie ein Element aktualisieren möchten, müssen Sie dieses Element in der Menge finden, es entfernen und den aktualisierten Wert des Elements einfügen. Da Sie jedoch die Werte aller Elemente aktualisieren müssen, müssen Sie alle Elemente einzeln löschen und einfügen.

Es ist möglich, dies in einer bereitgestellten for-Schleife zu tun sub > 0. S.erase(pos)Entfernt den Iterator an der Position posund gibt die folgende Position zurück. Wennsub > 0 , wird der aktualisierte Wert, den Sie einfügen, vor dem Wert am neuen Iterator im Baum stehen, aber wenn sub <= 0, dann wird der aktualisierte Wert nach dem Wert am neuen Iterator im Baum stehen und Sie werden daher in einem Endlosschleife.

for (auto itr = S.begin(); itr != S.end(); )
{
    int val = *itr;
    itr = S.erase(itr);
    S.insert(val - sub);
}
lucieon
quelle
Das ist ein guter Weg ... Ich denke, es ist nur ein Weg, dies zu tun. Einfach löschen und erneut einfügen.
Imtiaz Mehedi
3

Der Fehler erklärt das Problem ziemlich genau

Mitglieder des std::setContainers sindconst . Wenn Sie sie ändern, wird ihre jeweilige Bestellung ungültig.

Zum Ändern von Elementen in std::set , müssen Sie das Element löschen und nach dem erneut einfügen.

Alternativ können Sie std::mapdieses Szenario verwenden , um es zu überwinden.

P0W
quelle