Establecer operación en c ++ (actualizar el valor existente)

21

Aquí está mi código:

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

No puedo actualizar el valor de establecer por iterador. Quiero restar un valor entero 'sub' de todos los elementos del conjunto.

¿Alguien puede ayudarme dónde está el problema real y cuál sería la solución real?

Aquí está el mensaje de error:

error: assignment of read-only location it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~
Imtiaz Mehedi
fuente
1
Actualice a un ejemplo reproducible mínimo .
Yunnosch
99
Los elementos del conjunto solo se pueden leer. Al modificar uno, reordenaría otros elementos del conjunto.
rafix07
3
La solución es borrar el iterador e insertar uno nuevo con la clave *it - sub. Tenga en cuenta que std::set::erase()devuelve un nuevo iterador que debe usarse en su caso para mantener el whilebucle funcionando correctamente.
Scheff
2
@Scheff ¿Puedes hacer eso mientras estás girando sobre un set? ¿No podría terminar en un bucle sin fin? ¿Especialmente cuando se hace algo relevante para el conjunto iterado actualmente que coloca los elementos visitados donde serán visitados nuevamente?
Yunnosch
1
Imtiaz Por curiosidad, en caso de que haya un seguimiento para esta tarea, ¿podría informarlo aquí en un comentario (supongo que esta es una tarea sin que signifique nada malo, su pregunta está bien)? Como puede ver en mis comentarios sobre la respuesta de Scheff, estoy especulando sobre el plan más amplio de los maestros con esto. Sólo por curiosidad.
Yunnosch

Respuestas:

22

Los valores clave de los elementos en a std::setson constpor una buena razón. Modificarlos puede destruir el orden que es esencial para a std::set.

Por lo tanto, la solución es borrar el iterador e insertar uno nuevo con la clave *it - sub. Tenga en cuenta que std::set::erase()devuelve un nuevo iterador que debe usarse en su caso para mantener el bucle while funcionando correctamente.

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

Salida:

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

Demo en vivo en coliru


Los cambios al std::setiterar sobre él no son un problema en general, pero pueden causar problemas sutiles.

El hecho más importante es que todos los iteradores usados ​​deben mantenerse intactos o ya no pueden usarse. (Es por eso que el iterador actual del elemento de borrado se asigna con el valor de retorno del std::set::erase()cual es un iterador intacto o el final del conjunto).

Por supuesto, los elementos se pueden insertar también detrás del iterador actual. Si bien esto no es un problema relacionado con el std::set, puede romper el ciclo de mi ejemplo anterior.

Para demostrarlo, cambié un poco la muestra anterior. Tenga en cuenta que agregué un contador adicional para otorgar la terminación del ciclo:

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

Salida:

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

Demo en vivo en coliru

Scheff
fuente
1
¿Es posible que esto solo funcione para restar algo, pero podría terminar en un bucle sin fin si la operación provoca la reinserción en un lugar donde será visitado más tarde ...?
Yunnosch
@Yunnosch Además del peligro del bucle infinito, no hay ningún problema para insertar iteradores detrás del iterador actual. Los iteradores son estables en el std::set. Puede ser necesario considerar el caso límite en el que el nuevo iterador se inserta directamente detrás del borrado. - Se omitirá después de la inserción en el bucle.
Scheff
3
En C ++ 17, puede extractnodos, modificar sus claves y volverlos a configurar. Sería más eficiente, ya que evita asignaciones innecesarias.
Daniel Langr
¿Podría elaborar "Se omitirá después de la inserción en el bucle". Creo que no entiendo tu punto allí.
Yunnosch
2
Otro problema es que el valor de un elemento restado podría ser el mismo que uno de los valores aún no procesados ​​en std::set. Como no puede tener el mismo elemento dos veces, la inserción solo dejará el std::setmismo y perderá el elemento más tarde. Considere, por ejemplo, el conjunto de entrada: {10, 20, 30}con add = 10.
ComicSansMS
6

Simple simplemente reemplazarlo con otro conjunto

std::set<int> copy;

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

s.swap(copy);
acraig5075
fuente
5

No puede mutar elementos de std::setdiseño. Ver

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

Debido a que tanto iterator como const_iterator son iteradores constantes (y de hecho pueden ser del mismo tipo), no es posible mutar los elementos del contenedor a través de un iterador devuelto por cualquiera de estas funciones miembro.

Eso es porque el conjunto está ordenado . Si muta un elemento en una colección ordenada, la colección debe ordenarse nuevamente, lo que por supuesto es posible, pero no de la manera C ++.

Sus opciones son:

  1. Use otro tipo de colección (sin clasificar).
  2. Crea un nuevo conjunto y llénalo con elementos modificados.
  3. Elimine un elemento std::set, modifíquelo, luego insértelo nuevamente. (No es una buena idea si desea modificar cada elemento)
x00
fuente
4

A std::setse implementa típicamente como un árbol binario de equilibrio automático en STL. *ites el valor del elemento que se usa para ordenar el árbol. Si fuera posible modificarlo, el pedido se volvería inválido, por lo tanto, no es posible hacerlo.

Si desea actualizar un elemento, debe encontrar ese elemento en el conjunto, eliminarlo e insertar el valor actualizado del elemento. Pero como debe actualizar los valores de todos los elementos, debe borrar e insertar todos los elementos uno por uno.

Es posible hacerlo en uno para el bucle proporcionado sub > 0. S.erase(pos)elimina el iterador en la posición posy devuelve la siguiente posición. Si sub > 0, el valor actualizado que insertará vendrá antes del valor en el nuevo iterador en el árbol pero si sub <= 0, entonces el valor actualizado vendrá después del valor en el nuevo iterador en el árbol y, por lo tanto, terminará en un Bucle infinito.

for (auto itr = S.begin(); itr != S.end(); )
{
    int val = *itr;
    itr = S.erase(itr);
    S.insert(val - sub);
}
lucieon
fuente
Esa es una buena manera ... Creo que es la única forma de hacerlo. Simplemente elimine e inserte nuevamente.
Imtiaz Mehedi
3

El error explica más o menos el problema.

Los miembros del std::setcontenedor son const. Al cambiarlos, sus respectivos pedidos no son válidos.

Para cambiar elementos std::set, tendrá que borrar el elemento y volver a insertarlo después de cambiarlo.

Alternativamente, podría usar std::mappara superar este escenario.

P0W
fuente