Встановити операцію в c ++ (оновити існуюче значення)


21

Ось мій код:

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

Я не можу оновити значення, встановлене ітератором. Я хочу відняти ціле значення 'sub' з усіх елементів набору.

Чи може хто-небудь допомогти мені, де актуальна проблема і що було б фактичним рішенням?

Ось повідомлення про помилку:

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

1
Будь ласка, перейдіть на мінімально відтворюваний приклад .
Yunnosch

9
Елементи в наборі можна читати лише. Змінивши один, ви будете упорядкувати інші елементи в наборі.
rafix07

3
Рішення - стерти ітератор і вставити новий ключ *it - sub. Зауважте, що std::set::erase()повертає новий ітератор, який повинен бути використаний у вашому випадку, щоб whileцикл працював належним чином.
Scheff

2
@Scheff Чи можете ви це зробити, поки ви балуєтесь над набором? Чи не могло воно закінчитися в нескінченному циклі? Особливо, коли ви робите щось, що стосується ітературного набору, який розміщує відвідувані елементи там, де їх знову відвідуватимуть?
Yunnosch

1
Імтіаз З цікавості, якщо ви маєте подальше спостереження за цим завданням, чи можете ви повідомити про це тут у коментарі (я припускаю, що це завдання, не маючи на увазі нічого поганого, ваше питання добре)? Як ви бачите в моїх коментарях до відповіді Шеффа, я спекулюю на цьому планами більшого плану вчителів. Просто цікавість.
Yunnosch

Відповіді:


22

Ключові значення елементів у a std::setє constз поважної причини. Змінення їх може зруйнувати порядок, який є важливим для а std::set.

Отже, рішення - стерти ітератор і вставити новий ключ *it - sub. Зверніть увагу, що std::set::erase()повертає новий ітератор, який потрібно використовувати у вашому випадку, щоб цикл у той час працював належним чином.

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

Вихід:

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

Демонстрація демо на колиру


Зміни під std::setчас ітерації над нею взагалі не є проблемою, але можуть спричинити тонкі проблеми.

Найважливіший факт полягає в тому, що всі використовувані ітератори повинні залишатися недоторканими або більше не можуть використовуватися. (Ось чому поточному ітератору елемента стирання присвоюється повернене значення, std::set::erase()яке є або неушкодженим ітератором, або кінцем набору.)

Звичайно, елементи можна вставити і за поточним ітератором. Хоча це не є проблемою щодо цього, std::setце може зламати цикл мого вищевказаного прикладу.

Щоб продемонструвати це, я трохи змінив вищевказаний зразок. Зауважте, що я додав додатковий лічильник, щоб надати припинення циклу:

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

Вихід:

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

Демонстрація демо на колиру


1
Чи можливо, це працює лише для віднімання чогось, але може закінчитися нескінченним циклом, якщо операція спричинить перевстановлення в місці, де воно буде відвідано пізніше ....?
Yunnosch

@Yunnosch Крім небезпеки для нескінченного циклу, немає проблеми вставити ітератори за поточним ітератором. Ітератори стабільні в std::set. Можливо, слід врахувати випадок кордону, коли новий ітератор вставляється безпосередньо за стираним. - Він буде пропущений після вставки в цикл.
Scheff

3
У C ++ 17 ви можете extractвузли, змінювати їхні ключі та повертати їх у встановлені. Це було б більш ефективно, оскільки це дозволяє уникнути зайвих виділень.
Даніель Лангр

Не могли б ви пояснити "Це буде пропущено після вставки в цикл." Я думаю, що я не розумію там.
Yunnosch

2
Інша проблема полягає в тому, що значення відніманого елемента може бути таким же, як одне з ще не оброблених значень у std::set. Оскільки ви не можете мати один і той же елемент двічі, вставка просто залишиться std::setнезмінною, і ви втратите елемент пізніше. Розглянемо для прикладу вхідний набір: {10, 20, 30}с add = 10.
ComicSansMS

6

Просто замінити його іншим набором

std::set<int> copy;

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

s.swap(copy);

5

Ви не можете мутувати елементи std::setдизайну. Подивитися

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

Оскільки ітератор, так і const_iterator є постійними ітераторами (і насправді можуть бути одного типу), неможливо мутувати елементи контейнера через ітератор, повернутий будь-якою з цих функцій-членів.

Це тому, що набір сортується . Якщо ви мутуєте елемент у відсортованій колекції, колекція повинна бути відсортована ще раз, що, звичайно, можливо, але не C ++ способом.

Ваші варіанти:

  1. Використовуйте інший тип колекції (несортований).
  2. Створіть новий набір і заповніть його зміненими елементами.
  3. Видаліть елемент std::set, змініть його та вставте знову. (Недоцільно, якщо ви хочете змінити кожен елемент)

4

А std::setзазвичай реалізується як самоврівноважуване бінарне дерево в STL. *it- це значення елемента, який використовується для замовлення дерева. Якщо б вдалося його змінити, наказ стане недійсним, отже, зробити це неможливо.

Якщо ви хочете оновити елемент, вам слід знайти цей елемент у наборі, видалити його та вставити оновлене значення елемента. Але оскільки вам доведеться оновлювати значення всіх елементів, то вам доведеться стирати і вставляти всі елементи один за одним.

Це можна зробити в одному для певного циклу sub > 0. S.erase(pos)видаляє ітератор у положенні posта повертає наступне положення. Якщо sub > 0оновлене значення, яке ви будете вставляти, буде перед значенням у новому ітераторі в дереві, але якщо sub <= 0, то оновлене значення настане після значення на новому ітераторі в дереві, а значить, ви отримаєте в нескінченна петля.

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

Це хороший спосіб ... Я думаю, що це єдиний спосіб зробити це, просто видалити та знову вставити.
Імтіаз Мехеді

3

Помилка в значній мірі пояснює проблему

Члени std::setконтейнера є const. Змінивши їх, вони роблять своє замовлення недійсним.

Щоб змінити елементи в std::set, вам доведеться стерти елемент і знову вставити його після його зміни.

Крім того, ви можете використовувати std::mapдля подолання цього сценарію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.