Imposta operazione in c ++ (aggiorna valore esistente)


21

Ecco il mio codice:

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

Non riesco ad aggiornare il valore di set da iteratore. Voglio sottrarre un valore intero 'sub' da tutto l'elemento dell'insieme.

Qualcuno può aiutarmi dove si trova il problema reale e quale sarebbe la soluzione effettiva?

Ecco il messaggio di errore:

error: assignment of read-only location it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~


9
Gli elementi nel set possono essere solo letti. Modificandone uno, riordinerai gli altri elementi nel set.
rafix07,

3
La soluzione è cancellare l'iteratore e inserirne uno nuovo con chiave *it - sub. Tieni presente che std::set::erase()restituisce un nuovo iteratore che deve essere utilizzato nel tuo caso per far whilefunzionare correttamente il loop.
Scheff

2
@Scheff Puoi farlo mentre stai guardando un set? Non potrebbe finire in un ciclo infinito? Soprattutto quando si fa qualcosa di rilevante per l'insieme correntemente iterato che mette gli elementi visitati dove saranno visitati di nuovo?
Yunnosch,

1
Imtiaz Per curiosità, nel caso in cui ci fosse un seguito per questo incarico, potresti segnalarlo qui in un commento (suppongo che questo sia un incarico senza significato nulla di negativo, la tua domanda va bene)? Come puoi vedere nei miei commenti sulla risposta di Scheff, sto speculando sul piano più ampio degli insegnanti con questo. Solo curiosità.
Yunnosch,

Risposte:


22

I valori chiave degli elementi in a std::setsono constper una buona ragione. La loro modifica può distruggere l'ordine che è essenziale per a std::set.

Quindi, la soluzione è cancellare l'iteratore e inserirne uno nuovo con chiave *it - sub. Tieni presente che std::set::erase()restituisce un nuovo iteratore che deve essere utilizzato nel tuo caso per far funzionare correttamente il ciclo while.

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

Produzione:

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

Demo live su coliru


I cambiamenti durante l' std::setiterazione su di esso non sono un problema in generale, ma possono causare problemi sottili.

Il fatto più importante è che tutti gli iteratori utilizzati devono essere mantenuti intatti o non possono più essere utilizzati. (Ecco perché l'attuale iteratore dell'elemento di cancellazione viene assegnato con il valore di ritorno di std::set::erase()cui è un iteratore intatto o la fine dell'insieme.)

Naturalmente, è possibile inserire elementi anche dietro l'attuale iteratore. Anche se questo non è un problema in merito std::set, potrebbe interrompere il ciclo del mio esempio precedente.

Per dimostrarlo, ho modificato un po 'l'esempio sopra. Si prega di notare che ho aggiunto un contatore aggiuntivo per garantire la chiusura del loop:

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

Produzione:

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

Demo live su coliru


1
È possibile che questo funzioni solo per sottrarre qualcosa, ma potrebbe finire in un ciclo infinito se l'operazione provoca il reinserimento in un luogo in cui verrà visitata in seguito ....?
Yunnosch,

@Yunnosch Oltre al pericolo di loop infiniti, non vi è alcun problema a inserire iteratori dietro l'attuale iteratore. Gli iteratori sono stabili nel file std::set. Potrebbe essere necessario considerare il caso di confine in cui il nuovo iteratore viene inserito direttamente dietro quello cancellato. - Verrà saltato dopo l'inserimento nel loop.
Scheff

3
In C ++ 17, puoi extractnodi, modificarne le chiavi e riportarli in set. Sarebbe più efficiente, poiché evita allocazioni inutili.
Daniel Langr,

Potresti elaborare "Verrà ignorato dopo l'inserimento nel loop.". Penso di non capire il punto.
Yunnosch,

2
Un altro problema è che il valore di un elemento sottratto potrebbe essere lo stesso di uno dei valori non ancora elaborati in std::set. Dal momento che non è possibile avere lo stesso elemento due volte, l'inserimento lascerà semplicemente std::setinvariato e si perderà l'elemento in un secondo momento. Si consideri ad esempio il set di input: {10, 20, 30}con add = 10.
ComicSansMS

6

Semplice da sostituire con un altro set

std::set<int> copy;

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

s.swap(copy);

5

Non puoi mutare elementi std::setper design. Vedere

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

Poiché sia ​​iteratore che const_iterator sono iteratori costanti (e possono in effetti essere dello stesso tipo), non è possibile mutare gli elementi del contenitore attraverso un iteratore restituito da una di queste funzioni membro.

Questo perché set è ordinato . Se mutate un elemento in una raccolta ordinata, la raccolta deve essere ordinata di nuovo, il che è ovviamente possibile, ma non nel modo C ++.

Le tue opzioni sono:

  1. Utilizza un altro tipo di raccolta (non ordinata).
  2. Crea un nuovo set e riempilo con elementi modificati.
  3. Rimuovere un elemento da std::set, modificarlo, quindi inserirlo di nuovo. (Non è una buona idea se si desidera modificare ogni elemento)

4

A std::setè in genere implementato come albero binario autobilanciante in STL. *itè il valore dell'elemento utilizzato per ordinare l'albero. Se fosse possibile modificarlo, l'ordine diventerebbe non valido quindi non è possibile farlo.

Se vuoi aggiornare un elemento, devi trovare quell'elemento nel set, rimuoverlo e inserire il valore aggiornato dell'elemento. Ma poiché è necessario aggiornare i valori di tutti gli elementi, è necessario cancellare e inserire tutti gli elementi uno per uno.

È possibile farlo in uno per il ciclo fornito sub > 0. S.erase(pos)rimuove l'iteratore in posizione pose restituisce la seguente posizione. Se sub > 0, il valore aggiornato che inserirai verrà prima del valore nel nuovo iteratore nella struttura ma se sub <= 0, quindi il valore aggiornato verrà dopo il valore nel nuovo iteratore nella struttura e quindi finirai in un ciclo infinito.

for (auto itr = S.begin(); itr != S.end(); )
{
    int val = *itr;
    itr = S.erase(itr);
    S.insert(val - sub);
}

Questo è un buon modo ... Penso che sia l'unico modo per farlo. Basta eliminare e inserire di nuovo.
Imtiaz Mehedi

3

L'errore spiega praticamente il problema

I membri del std::setcontenitore sono const. La loro modifica rende non valido il rispettivo ordine.

Per cambiare gli elementi in std::set, dovrai cancellare l'elemento e reinserirlo dopo che è stato modificato.

In alternativa, è possibile utilizzare std::mapper superare questo scenario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.