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

반복자가 설정 한 값을 업데이트 할 수 없습니다. set의 모든 요소에서 정수 값 '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 당신은 세트를 통해 ierating 할 수 있습니까? 끝없는 루프로 끝날 수 없었습니까? 특히 방문한 요소를 다시 방문하는 요소를 배치하는 현재 반복되는 세트와 관련된 작업을 수행 할 때?
Yunnosch

1
Imtiaz 호기심에서이 과제에 대한 후속 조치가있을 경우 여기에 의견으로보고 할 수 있습니까? Scheff 답변에 대한 내 의견에서 볼 수 있듯이 교사들에게 더 큰 계획에 대해 추측하고 있습니다. 그냥 호기심.
Yunnosch

답변:


22

A의 원소의 키 값 std::set입니다 const좋은 이유. 수정하면에 필수적인 순서가 손상 될 수 있습니다 std::set.

따라서 해결책은 반복자를 지우고 키를 사용하여 새 반복자를 삽입하는 것입니다 *it - sub. std::set::erase()while 루프가 제대로 작동하도록하려면 새 iterator 를 반환해야합니다.

#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노드를 키를 수정하고 키를 다시 설정으로 되돌릴 수 있습니다. 불필요한 할당을 피하기 때문에 더 효율적입니다.
Daniel Langr

"루프에 삽입 한 후에는 건너 뜁니다." 나는 당신의 포인트를 거기에서 얻지 못한다고 생각합니다.
Yunnosch

2
또 다른 문제는 빼기 요소의 값이에서 아직 처리되지 않은 값 중 하나와 동일 할 수 있다는 것입니다 std::set. 동일한 요소를 두 번 가질 수 없으므로 삽입하면 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

iterator와 const_iterator는 모두 일정한 반복자 (실제로 같은 유형일 수 있음)이기 때문에 이러한 멤버 함수 중 하나에 의해 리턴 된 반복자를 통해 컨테이너 요소를 변경할 수 없습니다.

set이 정렬 되었기 때문입니다 . 정렬 된 컬렉션에서 요소를 변경하면 컬렉션을 다시 정렬해야합니다. 물론 가능하지만 C ++ 방식은 아닙니다.

옵션은 다음과 같습니다.

  1. 다른 유형의 컬렉션 (정렬되지 않은)을 사용하십시오.
  2. 새 세트를 작성하고 수정 된 요소로 채 웁니다.
  3. 에서 요소를 제거하고 std::set수정 한 다음 다시 삽입하십시오. (모든 요소를 ​​수정하려는 경우 좋지 않습니다)

4

std::set 는 일반적으로 STL에서 자체 균형 이진 트리로 구현됩니다. *it트리를 주문하는 데 사용되는 요소의 값입니다. 주문을 수정할 수 있으면 주문이 유효하지 않으므로 그렇게 할 수 없습니다.

요소를 업데이트하려면 세트에서 해당 요소를 찾아 제거하고 업데이트 된 요소 값을 삽입해야합니다. 그러나 모든 요소의 값을 업데이트해야하므로 모든 요소를 ​​하나씩 지우고 삽입해야합니다.

제공된 for 루프로 수행 할 수 있습니다 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);
}

좋은 방법입니다 ... 나는 그것을 할 수있는 유일한 방법이라고 생각합니다. 삭제하고 다시 삽입하십시오.
Imtiaz Mehedi

3

오류는 거의 문제를 설명합니다

std::set컨테이너 멤버 는 const입니다. 변경하면 해당 주문이 유효하지 않습니다.

의 요소를 변경 std::set하려면 항목을 지우고 변경 한 후 다시 삽입해야합니다.

또는 std::map이 시나리오를 극복 하는 데 사용할 수 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.