Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set operation in c++(update existing value)

Tags:

c++

Here is my 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++;
    }

I can't update the value of set by iterator. I want to subtract an integer value 'sub' from all the element of set.

Can anyone help me where the actual problem is and what would be the actual solution?

Here is the error message:

error: assignment of read-only location ‘it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~
like image 299
Imtiaz Mehedi Avatar asked Jan 16 '20 06:01

Imtiaz Mehedi


3 Answers

Key values of elements in a std::set are const for a good reason. Modifying them may destroy the order which is essential for a std::set.

Hence, the solution is to erase the iterator and insert a new one with key *it - sub. Please, note that std::set::erase() returns a new iterator which has to be used in your case to keep the while loop working properly.

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

Output:

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

Live Demo on coliru


Changes on the std::set while iterating over it are not a problem in general but can cause subtle issues.

The most important fact is that all used iterators have to be kept intact or may not be used anymore. (That's why the current iterator of the erase element is assigned with the return value of std::set::erase() which is either an intact iterator or the end of set.)

Of course, elements can be inserted as well behind the current iterator. While this is not a problem concerning the std::set it may break the loop of my above example.

To demonstrate it, I changed the above sample a bit. Please, note that I added an additional counter to grant the termination of 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';
}

Output:

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

Live Demo on coliru

like image 100
Scheff's Cat Avatar answered Oct 03 '22 14:10

Scheff's Cat


Simple to just replace it with another set

std::set<int> copy;

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

s.swap(copy);
like image 44
acraig5075 Avatar answered Oct 03 '22 14:10

acraig5075


You can't mutate elements of std::set by design. See

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

Because both iterator and const_iterator are constant iterators (and may in fact be the same type), it is not possible to mutate the elements of the container through an iterator returned by any of these member functions.

That is because set is sorted. If you mutate element in a sorted collection the collection must be sorted again, which is of course possible, but not the C++ way.

Your options are:

  1. Use another type of collection (unsorted).
  2. Create a new set and fill it with modified elements.
  3. Remove an element from std::set, modify it, then insert again. (It is not a good idea if you want to modify every element)
like image 23
x00 Avatar answered Oct 03 '22 15:10

x00