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
最小限の再現可能な例にアップグレードしてください。
ユンノシュ

9
セット内の要素は読み取り専用です。1つを変更すると、セット内の他のアイテムを並べ替えることになります。
rafix07

3
解決策は、イテレータを消去し、キーで新しいイテレータを挿入すること*it - subです。ループが適切に機能std::set::erase()し続けるためにケースで使用する必要がある新しいイテレータを返すことに注意してくださいwhile
Scheff

2
@Scheffセットで騒動しているときにそれを行うことはできますか?それは無限ループになってしまうのではないでしょうか?特に、現在の反復セットに関連する何かを実行するときに、訪問された要素が再び訪問される場所に配置されますか?
ユンノシュ

1
Imtiaz好奇心から、この割り当てのフォローアップがある場合は、コメントでここに報告できますか(これは悪いことを意味することのない割り当てだと思いますが、質問は大丈夫です)?Scheffの回答に対する私のコメントでわかるように、私はこれについて教師のより大きな計画について推測しています。ただ好奇心。
ユンノシュ

回答:


22

要素のキー値がstd::setありconst、正当な理由のために。それらを変更すると、に不可欠な順序が破壊される可能性がありstd::setます。

したがって、解決策はイテレータを消去し、キーを使用して新しいイテレータを挿入すること*it - subです。std::set::erase()は、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';
}

出力:

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

コリルのライブデモ


std::set繰り返し処理中の変更は一般的に問題ではありませんが、微妙な問題を引き起こす可能性があります。

最も重要な事実は、使用されるすべてのイテレータはそのままにしておく必要があるか、もう使用しない可能性があるということです。(それが、erase要素の現在のイテレーターに、戻り値が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無限ループの危険性のほかに、現在のイテレーターの後ろにイテレーターを挿入しても問題はありません。イテレータはで安定していstd::setます。新しいイテレータが消去されたもののすぐ後ろに挿入されるという境界のケースを考慮する必要があるかもしれません。-ループへの挿入後にスキップされます。
Scheff

3
C ++ 17では、extractノードを作成し、そのキーを変更して、それらをセットに戻すことができます。不要な割り当てを回避できるため、より効率的です。
Daniel Langr

「ループへの挿入後にスキップされます。」について詳しく説明してください。私はそこにあなたの要点を理解していないと思います。
ユンノシュ

2
別の問題は、差し引かれた要素の値が、の未処理の値の1つと同じになる可能性があることstd::setです。同じ要素を2回持つことはできないため、挿入してもstd::set変更されないままになり、後で要素が失われます。たとえば、入力セットを考えてみましょう:{10, 20, 30}with 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

A std::setは通常、STLの自己平衡バイナリツリーとして実装されます。*itツリーの順序付けに使用される要素の値です。それを変更することができた場合、注文は無効になるため、それを行うことはできません。

要素を更新する場合は、セット内でその要素を見つけて削除し、更新された要素の値を挿入する必要があります。ただし、すべての要素の値を更新する必要があるため、すべての要素を1つずつ消去して挿入する必要があります。

提供されている1つのforループでそれを行うことができますsub > 0S.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);
}

それは良い方法です...それはそれを行う唯一の方法だと思います。削除してもう一度挿入するだけです。
Imtiaz Mehedi

3

エラーは問題をかなり説明しています

std::setコンテナのメンバーはconstです。それらを変更すると、それぞれの順序が無効になります。

で要素を変更std::setするには、アイテムを消去して、変更後に再度挿入する必要があります。

または、を使用std::mapしてこのシナリオを克服することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.