Atur operasi dalam c ++ (perbarui nilai yang ada)

21

Ini kode saya:

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

Saya tidak dapat memperbarui nilai yang ditetapkan oleh iterator. Saya ingin mengurangi nilai integer 'sub' dari semua elemen set.

Adakah yang bisa membantu saya di mana masalah sebenarnya dan apa solusi yang sebenarnya?

Ini pesan kesalahannya:

error: assignment of read-only location it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~
Imtiaz Mehedi
sumber
1
Harap tingkatkan ke contoh minimal yang dapat direproduksi .
Yunnosch
9
Elemen dalam set hanya dapat dibaca. Dengan memodifikasi satu, Anda akan menyusun ulang item lain di set.
rafix07
3
Solusinya adalah menghapus iterator dan memasukkan yang baru dengan kunci *it - sub. Harap perhatikan bahwa std::set::erase()mengembalikan iterator baru yang harus digunakan dalam kasus Anda agar whileloop tetap berfungsi dengan baik.
Scheff
2
@Scheff Bisakah Anda melakukan itu saat Anda mengulangi satu set? Mungkinkah itu berakhir dalam satu lingkaran tanpa akhir? Terutama ketika melakukan sesuatu yang relevan dengan himpunan iterasi yang saat ini menempatkan elemen yang dikunjungi di mana mereka akan dikunjungi lagi?
Yunnosch
1
Imtiaz Karena penasaran, kalau-kalau ada tindak lanjut untuk tugas ini, dapatkah Anda melaporkannya di sini dalam komentar (Saya menganggap ini adalah tugas tanpa berarti sesuatu yang buruk dengan itu, pertanyaan Anda baik-baik saja)? Seperti yang Anda lihat dalam komentar saya tentang jawaban Scheff, saya berspekulasi tentang rencana guru yang lebih besar dengan ini. Hanya ingin tahu.
Yunnosch

Jawaban:

22

Nilai-nilai kunci elemen dalam std::setadalah constuntuk alasan yang bagus. Mengubah mereka dapat merusak urutan yang penting untuk a std::set.

Oleh karena itu, solusinya adalah menghapus iterator dan memasukkan yang baru dengan kunci *it - sub. Harap perhatikan bahwa std::set::erase()mengembalikan iterator baru yang harus digunakan dalam kasus Anda agar loop sementara berfungsi dengan baik.

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

Keluaran:

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

Demo langsung di coliru


Perubahan pada std::setsaat iterasi itu bukan masalah secara umum tetapi dapat menyebabkan masalah halus.

Fakta yang paling penting adalah bahwa semua iterator yang digunakan harus tetap utuh atau tidak dapat digunakan lagi. (Itulah sebabnya iterator saat ini dari elemen erase ditetapkan dengan nilai pengembalian std::set::erase()yang mana merupakan iterator utuh atau akhir set.)

Tentu saja, elemen dapat dimasukkan juga di belakang iterator saat ini. Meskipun ini bukan masalah mengenai hal std::setitu dapat memutus loop dari contoh saya di atas.

Untuk menunjukkannya, saya mengubah sampel di atas sedikit. Harap perhatikan bahwa saya menambahkan penghitung tambahan untuk memberikan penghentian 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';
}

Keluaran:

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

Demo langsung di coliru

Scheff
sumber
1
Mungkinkah ini hanya berfungsi untuk mengurangi sesuatu, tetapi bisa berakhir dalam loop tanpa akhir jika operasi menyebabkan pemasukan kembali di tempat yang akan dikunjungi nanti ....?
Yunnosch
@Yunnosch Selain bahaya untuk infinite loop, tidak ada masalah untuk memasukkan iterator di belakang iterator saat ini. Iterator stabil di Internet std::set. Mungkin perlu untuk mempertimbangkan kasus perbatasan bahwa iterator baru dimasukkan langsung di belakang terhapus. - Ini akan dilewati setelah penyisipan dalam loop.
Scheff
3
Di C ++ 17, Anda dapat membuat extractsimpul, memodifikasi kunci mereka, dan mengembalikannya ke pengaturan. Akan lebih efisien, karena menghindari alokasi yang tidak perlu.
Daniel Langr
Bisakah Anda menguraikan "Ini akan dilewati setelah penyisipan dalam loop.". Saya pikir saya tidak mengerti maksud Anda di sana.
Yunnosch
2
Masalah lain adalah bahwa nilai elemen yang dikurangi mungkin sama dengan salah satu nilai yang belum diproses di std::set. Karena Anda tidak dapat memiliki elemen yang sama dua kali, penyisipan hanya akan meninggalkan yang std::settidak berubah dan Anda akan kehilangan elemen nanti. Pertimbangkan misalnya set input: {10, 20, 30}dengan add = 10.
ComicSansMS
6

Sederhana hanya dengan menggantinya dengan set lain

std::set<int> copy;

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

s.swap(copy);
acraig5075
sumber
5

Anda tidak dapat mengubah elemen std::setmenurut desain. Lihat

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

Karena kedua iterator dan const_iterator adalah iterator konstan (dan mungkin sebenarnya adalah tipe yang sama), tidak mungkin untuk memutasi elemen-elemen dari wadah melalui iterator yang dikembalikan oleh salah satu fungsi anggota ini.

Itu karena set diurutkan . Jika Anda bermutasi elemen dalam koleksi yang diurutkan koleksi harus diurutkan lagi, yang tentu saja mungkin, tetapi tidak dengan cara C ++.

Pilihan Anda adalah:

  1. Gunakan jenis koleksi lain (tidak disortir).
  2. Buat set baru dan isi dengan elemen yang dimodifikasi.
  3. Hapus elemen dari std::set, modifikasi, lalu masukkan lagi. (Ini bukan ide yang baik jika Anda ingin memodifikasi setiap elemen)
x00
sumber
4

A std::setbiasanya diimplementasikan sebagai pohon biner self-balancing di STL. *itadalah nilai elemen yang digunakan untuk memesan pohon. Jika dimungkinkan untuk memodifikasinya, pesanan akan menjadi tidak valid sehingga tidak mungkin untuk melakukannya.

Jika Anda ingin memperbarui elemen, maka Anda harus menemukan elemen itu di set, hapus dan masukkan nilai elemen yang diperbarui. Tetapi karena Anda harus memperbarui nilai semua elemen, maka Anda harus menghapus dan memasukkan semua elemen satu per satu.

Dimungkinkan untuk melakukannya dalam satu untuk loop yang disediakan sub > 0. S.erase(pos)menghapus iterator di posisi posdan mengembalikan posisi berikut. Jika sub > 0, nilai yang diperbarui yang akan Anda masukkan akan datang sebelum nilai di iterator baru di pohon tetapi jika sub <= 0, maka nilai yang diperbarui akan datang setelah nilai di iterator baru di pohon dan karenanya Anda akan berakhir di sebuah loop tak terbatas.

for (auto itr = S.begin(); itr != S.end(); )
{
    int val = *itr;
    itr = S.erase(itr);
    S.insert(val - sub);
}
lucieon
sumber
Itu cara yang baik ... Saya pikir itu hanya cara untuk melakukan itu. Cukup hapus dan masukkan kembali.
Imtiaz Mehedi
3

Kesalahan cukup banyak menjelaskan masalahnya

Anggota std::setkontainer adalah const. Mengubahnya membuat pesanannya masing-masing tidak valid.

Untuk mengubah elemen std::set, Anda harus menghapus item dan memasukkannya kembali setelah diubah.

Atau, Anda bisa menggunakan std::mapuntuk mengatasi skenario ini.

P0W
sumber