Đặt thao tác trong c ++ (cập nhật giá trị hiện có)


21

Đây là mã của tôi:

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

Tôi không thể cập nhật giá trị được đặt bởi iterator. Tôi muốn trừ một giá trị nguyên 'phụ' khỏi tất cả các phần tử của tập hợp.

Bất cứ ai có thể giúp tôi vấn đề thực sự là gì và giải pháp thực tế là gì?

Đây là thông báo lỗi:

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

1
Vui lòng nâng cấp lên một ví dụ tái sản xuất tối thiểu .
Yunnosch

9
Các yếu tố trong bộ chỉ có thể được đọc. Bằng cách sửa đổi một, bạn sẽ sắp xếp lại các mục khác trong bộ.
rafix07

3
Giải pháp là xóa iterator và chèn một cái mới với khóa *it - sub. Xin lưu ý rằng std::set::erase()trả về một trình vòng lặp mới phải được sử dụng trong trường hợp của bạn để giữ cho whilevòng lặp hoạt động đúng.
Scheff

2
@Scheff Bạn có thể làm điều đó trong khi bạn đang lặp lại một bộ không? Nó có thể không kết thúc trong một vòng lặp vô tận? Đặc biệt là khi làm một cái gì đó có liên quan đến tập hợp lặp đi lặp lại mà đặt các yếu tố được truy cập, nơi chúng sẽ được truy cập lại?
Yunnosch

1
Imtiaz Vì tò mò, trong trường hợp có sự theo dõi cho bài tập này, bạn có thể báo cáo nó ở đây trong một bình luận (tôi cho rằng đây là một bài tập mà không có ý nghĩa gì xấu với nó, câu hỏi của bạn vẫn ổn)? Như bạn có thể thấy trong các nhận xét của tôi về câu trả lời của Scheff, tôi đang suy đoán về kế hoạch lớn hơn của giáo viên với điều này. Chỉ tò mò thôi.
Yunnosch

Câu trả lời:


22

Giá trị chính của các yếu tố trong một std::setconstvì một lý do tốt. Sửa đổi chúng có thể phá hủy thứ tự cần thiết cho a std::set.

Do đó, giải pháp là xóa iterator và chèn một cái mới bằng khóa *it - sub. Xin lưu ý rằng std::set::erase()trả về một trình vòng lặp mới phải được sử dụng trong trường hợp của bạn để giữ cho vòng lặp while hoạt động bình thường.

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

Đầu ra:

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

Demo trực tiếp trên coliru


Thay đổi std::settrong khi lặp đi lặp lại nó không phải là một vấn đề nói chung nhưng có thể gây ra các vấn đề tinh tế.

Thực tế quan trọng nhất là tất cả các trình vòng lặp đã sử dụng phải được giữ nguyên hoặc không được sử dụng nữa. (Đó là lý do tại sao bộ lặp hiện tại của phần tử xóa được gán với giá trị trả về std::set::erase()là bộ lặp nguyên vẹn hoặc phần cuối của bộ.)

Tất nhiên, các phần tử có thể được chèn vào phía sau trình lặp hiện tại. Mặc dù đây không phải là vấn đề liên quan đến việc std::setnó có thể phá vỡ vòng lặp của ví dụ trên của tôi.

Để chứng minh, tôi đã thay đổi mẫu trên một chút. Xin lưu ý rằng tôi đã thêm một bộ đếm bổ sung để cấp phép chấm dứt vòng lặp:

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

Đầu ra:

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

Demo trực tiếp trên coliru


1
Có thể là điều này chỉ hoạt động để trừ đi một cái gì đó, nhưng có thể kết thúc trong một vòng lặp vô tận nếu hoạt động gây ra sự tái cấu trúc tại một nơi mà nó sẽ được truy cập sau ....?
Yunnosch

@Yunnosch Bên cạnh sự nguy hiểm cho vòng lặp vô hạn, không có vấn đề gì khi chèn các trình vòng lặp phía sau trình vòng lặp hiện tại. Lặp đi lặp lại ổn định trong std::set. Có thể cần phải xem xét trường hợp đường viền mà trình vòng lặp mới được chèn trực tiếp phía sau phần bị xóa. - Nó sẽ bị bỏ qua sau khi chèn vào vòng lặp.
Scheff

3
Trong C ++ 17, bạn có thể extractnút, sửa đổi khóa của chúng và đưa chúng trở lại để thiết lập. Nó sẽ hiệu quả hơn, vì nó tránh được sự phân bổ không cần thiết.
Daniel Langr

Bạn có thể giải thích "Nó sẽ bị bỏ qua sau khi chèn vào vòng lặp." Tôi nghĩ rằng tôi không nhận được quan điểm của bạn ở đó.
Yunnosch

2
Một vấn đề khác là giá trị của một phần tử bị trừ có thể giống như một trong các giá trị chưa được xử lý trong std::set. Vì bạn không thể có cùng một phần tử hai lần, phần chèn sẽ std::setkhông thay đổi và bạn sẽ mất phần tử sau đó. Xem xét ví dụ bộ đầu vào: {10, 20, 30}với add = 10.
Truyện tranh

6

Đơn giản chỉ cần thay thế nó bằng một bộ khác

std::set<int> copy;

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

s.swap(copy);

5

Bạn không thể thay đổi các yếu tố của std::setthiết kế. Xem

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

Vì cả iterator và const_iterator đều là các iterator không đổi (và trên thực tế có thể là cùng một loại), nên không thể biến đổi các phần tử của container thông qua một iterator được trả về bởi bất kỳ hàm thành viên nào trong số này.

Đó là bởi vì bộ được sắp xếp . Nếu bạn thay đổi thành phần trong bộ sưu tập đã sắp xếp, bộ sưu tập phải được sắp xếp lại, điều này là tất nhiên có thể, nhưng không phải là cách C ++.

Lựa chọn của bạn là:

  1. Sử dụng một loại bộ sưu tập khác (chưa sắp xếp).
  2. Tạo một bộ mới và điền nó với các yếu tố sửa đổi.
  3. Xóa một phần tử khỏi std::set, sửa đổi nó, sau đó chèn lại. (Đó không phải là một ý tưởng tốt nếu bạn muốn sửa đổi mọi yếu tố)

4

Một std::set thường được triển khai như một cây nhị phân tự cân bằng trong STL. *itlà giá trị của phần tử được sử dụng để đặt hàng cây. Nếu có thể sửa đổi nó, thứ tự sẽ trở nên không hợp lệ do đó không thể làm điều đó.

Nếu bạn muốn cập nhật một phần tử, thì bạn phải tìm phần tử đó trong tập hợp, loại bỏ nó và chèn giá trị cập nhật của phần tử. Nhưng vì bạn phải cập nhật giá trị của tất cả các yếu tố, nên bạn phải xóa và chèn từng yếu tố một.

Có thể làm điều đó trong một vòng lặp được cung cấp sub > 0. S.erase(pos)loại bỏ iterator tại vị trí posvà trả về vị trí sau. Nếu sub > 0, giá trị được cập nhật mà bạn sẽ chèn sẽ đến trước giá trị tại trình lặp mới trong cây nhưng nếu sub <= 0, thì giá trị được cập nhật sẽ đến sau giá trị tại trình lặp mới trong cây và do đó bạn sẽ kết thúc bằng một vòng lặp vô hạn.

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

Đó là một cách tốt ... Tôi nghĩ rằng đó là cách duy nhất để làm điều đó. Xóa và thêm một lần nữa.
Imtiaz Mehedi

3

Lỗi khá nhiều giải thích vấn đề

Các thành viên của std::setcontainer là const. Thay đổi chúng làm cho thứ tự tương ứng của họ không hợp lệ.

Để thay đổi các thành phần trong std::set, bạn sẽ phải xóa mục đó và chèn lại sau khi thay đổi.

Ngoài ra, bạn có thể sử dụng std::mapđể khắc phục tình huống này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.