C ++ 'da işlemi ayarla (mevcut değeri güncelle)


21

İşte benim kod:

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

Yineleyici tarafından ayarlanan değeri güncelleyemiyorum. Ben kümenin tüm elemanından 'sub' bir tamsayı değeri çıkarmak istiyorum.

Asıl sorunun nerede olduğunu ve asıl çözümün ne olacağını kimse bana yardım edebilir mi?

Hata mesajı:

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


9
Kümedeki öğeler sadece okunabilir. Birini değiştirerek, kümedeki diğer öğeleri yeniden sıralayabilirsiniz.
rafix07

3
Çözüm, yineleyiciyi silmek ve anahtarla yeni bir tane eklemektir *it - sub. Döngünün düzgün çalışmasını std::set::erase()sağlamak için sizin durumunuzda kullanılması gereken yeni bir yineleyici döndürdüğünü lütfen unutmayın while.
Ocak'ta Scheff

2
@Scheff Bir setin üzerinde hareket ederken bunu yapabilir misin? Sonsuz bir döngü ile sonuçlanamaz mı? Özellikle, ziyaret edilen elemanları tekrar ziyaret edilecekleri yere koyan, sürekli yinelenen set ile ilgili bir şey yaparken?
Yunnosch

1
Imtiaz Meraktan, bu ödev için bir takip olması durumunda, bunu bir yorumda rapor edebilir misiniz (bunun kötü bir anlamı olmayan bir ödev olduğunu varsayıyorum, sorunuz iyi)? Scheff cevabı hakkındaki yorumlarımda görebileceğiniz gibi, öğretmenlerin bununla ilgili daha geniş bir plan hakkında spekülasyon yapıyorum. Sadece merak.
Yunnosch

Yanıtlar:


22

Bir elemanların anahtar değerleri std::setolan constiyi bir nedenle. Bunları değiştirmek a için gerekli olan düzeni yok edebilir std::set.

Bu nedenle, çözüm yineleyiciyi silmek ve anahtarla yeni bir tane eklemektir *it - sub. Lütfen std::set::erase()while döngüsünün düzgün çalışmasını sağlamak için sizin durumunuzda kullanılması gereken yeni bir yineleyici döndürdüğünü unutmayın .

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

Çıktı:

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

Coliru'da Canlı Demo


Üzerinde std::setyineleme yaparken yapılan değişiklikler genel olarak bir sorun değildir, ancak ince sorunlara neden olabilir.

En önemli gerçek, kullanılan tüm yineleyicilerin sağlam kalması veya artık kullanılmaması gerektiğidir. (Bu nedenle, silme öğesinin geçerli yineleyicisi, dönüş değeri std::set::erase()ya sağlam bir yineleyici veya kümenin sonu olan atanmış olarak atanır .)

Elbette, elemanlar mevcut yineleyicinin arkasına da eklenebilir. Bu bir sorun olmasa std::setda, yukarıdaki örneğimin döngüsünü kırabilir.

Göstermek için yukarıdaki örneği biraz değiştirdim. Döngünün sonlandırılması için ek bir sayaç eklediğimi lütfen unutmayın:

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

Çıktı:

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

Coliru'da Canlı Demo


1
Bu sadece bir şey çıkarmak için çalışır, ancak operasyon daha sonra ziyaret edilecek bir yerde yeniden yerleştirme neden olursa sonsuz bir döngü sonuçlanabilir ....?
Yunnosch

@Yunnosch Sonsuz döngü tehlikesinin yanı sıra, yineleyicileri geçerli yineleyicinin arkasına yerleştirmekte sorun yoktur. Yineleyiciler istikrarlı std::set. Yeni yineleyicinin doğrudan silinenin arkasına yerleştirildiği kenarlık durumunu dikkate almak gerekebilir. - Döngü içine yerleştirildikten sonra atlanacaktır.
Ocak'ta Scheff

3
C ++ 17'de extractdüğümleri değiştirebilir, anahtarlarını değiştirebilir ve sete geri döndürebilirsiniz. Gereksiz tahsisleri önlediğinden daha verimli olacaktır.
Daniel Langr

"Döngüye yerleştirildikten sonra atlanacaktır." Sanırım orada ne demek istediğini anlamıyorum.
Yunnosch

2
Başka bir sorun, çıkarılan bir öğenin değerinin, içindeki henüz işlenmemiş değerlerden biriyle aynı olabileceğidir std::set. Aynı öğeye iki kez sahip olamayacağınız için, ekleme yalnızca std::setdeğişmeden kalır ve öğeyi daha sonra kaybedersiniz. Örneğin: {10, 20, 30}ile girdi setini düşünün add = 10.
ComicSansMS

6

Başka bir setle değiştirmek basit

std::set<int> copy;

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

s.swap(copy);

5

std::setTasarım unsurlarını değiştiremezsiniz . Görmek

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

Hem yineleyici hem de const_iterator sabit yineleyiciler olduğundan (ve aslında aynı tipte olabileceğinden), kabın öğelerini bu üye işlevlerin herhangi biri tarafından döndürülen bir yineleyici aracılığıyla değiştirmek mümkün değildir.

Bunun nedeni setin sıralanmasıdır . Öğeyi sıralı bir koleksiyonda değiştirirseniz, koleksiyonun yeniden sıralanması gerekir, bu elbette mümkündür, ancak C ++ yolu değil.

Seçenekleriniz:

  1. Başka bir koleksiyon türü kullanın (sıralanmamış).
  2. Yeni bir küme oluşturun ve değiştirilmiş öğelerle doldurun.
  3. Bir öğeyi kaldırın std::set, değiştirin, sonra tekrar ekleyin. (Her öğeyi değiştirmek istiyorsanız iyi bir fikir değildir)

4

A std::settipik olarak STL'de kendi kendini dengeleyen bir ikili ağaç olarak uygulanır. *itağacı sipariş etmek için kullanılan elemanın değeridir. Değiştirmek mümkün olsaydı, sipariş geçersiz olur, bu yüzden bunu yapmak mümkün değildir.

Bir öğeyi güncellemek istiyorsanız, o öğeyi kümede bulmanız, kaldırmanız ve öğenin güncellenmiş değerini eklemeniz gerekir. Ancak tüm öğelerin değerlerini güncellemeniz gerektiğinden, tüm öğeleri tek tek silmeniz ve eklemeniz gerekir.

Sağlanan döngü için bir arada yapmak mümkündür sub > 0. S.erase(pos)yineleyiciyi konumunda kaldırır ve posaşağıdaki konumu döndürür. Ekleyeceğiniz sub > 0güncellenmiş değer, ağaçtaki yeni yineleyicideki değerden önce gelirse sub <= 0, eğer varsa , güncellenen değer, ağaçtaki yeni yineleyicideki değerden sonra gelir ve bu nedenle bir sonsuz döngü.

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

Bu iyi bir yol ... Bunu yapmanın tek yolu olduğunu düşünüyorum. Sadece silip tekrar ekleyin.
Imtiaz Mehedi

3

Hata sorunu hemen hemen açıklıyor

Üyeleri std::setkonteyner vardır const. Bunları değiştirmek, ilgili siparişlerini geçersiz kılar.

İçindeki öğeleri değiştirmek std::setiçin, öğeyi silmeniz ve değiştirildikten sonra yeniden eklemeniz gerekir.

Alternatif olarak, std::mapbu senaryonun üstesinden gelmek için de kullanabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.