ตั้งค่าการทำงานใน 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++;
    }

ฉันไม่สามารถอัปเดตค่าที่กำหนดโดยตัววนซ้ำ ฉันต้องการลบค่าจำนวนเต็ม 'ย่อย' จากองค์ประกอบทั้งหมดของชุด

ใครช่วยฉันได้บ้างว่าปัญหาที่แท้จริงคืออะไรและอะไรจะเป็นทางออกที่แท้จริง?

นี่คือข้อความแสดงข้อผิดพลาด:

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

1
โปรดอัปเกรดเป็นตัวอย่างที่สามารถทำซ้ำได้น้อยที่สุด
Yunnosch

9
องค์ประกอบในชุดสามารถอ่านได้เท่านั้น โดยการแก้ไขหนึ่งคุณจะเรียงลำดับรายการอื่น ๆ ในชุด
rafix07

3
การแก้ปัญหาคือการลบ iterator *it - subและใส่ใหม่ด้วยกุญแจ โปรดทราบว่าstd::set::erase()จะส่งคืนตัววนซ้ำใหม่ซึ่งจะต้องใช้ในกรณีของคุณเพื่อให้whileวงวนทำงานได้อย่างถูกต้อง
Scheff

2
@Seff คุณสามารถทำเช่นนั้นในขณะที่คุณกำลังชุดมากกว่า? มันจะไม่จบลงด้วยการวนซ้ำไม่รู้จบไหม? โดยเฉพาะอย่างยิ่งเมื่อทำอะไรบางอย่างที่เกี่ยวข้องกับชุดที่วนซ้ำอย่างเด่นชัดซึ่งทำให้องค์ประกอบที่เยี่ยมชมจะถูกเยี่ยมชมอีกครั้ง
Yunnosch

1
Imtiaz จากความอยากรู้ในกรณีที่มีการติดตามงานนี้คุณสามารถรายงานได้ที่นี่ในความคิดเห็น (ฉันคิดว่านี่เป็นงานที่มอบหมายโดยไม่มีความหมายอะไรที่ไม่ดีคำถามของคุณก็ดี)? อย่างที่คุณเห็นในความคิดเห็นของฉันเกี่ยวกับคำตอบของ Scheff ฉันกำลังคาดเดาเกี่ยวกับแผนการที่ใหญ่กว่าของครู แค่อยากรู้
Yunnosch

คำตอบ:


22

ค่าคีย์ขององค์ประกอบใน a std::setนั้นconstมีเหตุผลที่ดี std::setการปรับเปลี่ยนพวกเขาอาจทำลายการสั่งซื้อซึ่งเป็นสิ่งจำเป็นสำหรับ

ดังนั้นการแก้ปัญหาคือการลบ iterator *it - subและใส่ใหม่ด้วยกุญแจ โปรดทราบว่าstd::set::erase()จะส่งคืนตัววนซ้ำใหม่ซึ่งจะต้องใช้ในกรณีของคุณเพื่อให้ห่วงขณะทำงานอย่างถูกต้อง

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

การสาธิตสดบน coliru


การเปลี่ยนแปลงใน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 }

การสาธิตสดบน coliru


1
เป็นไปได้ไหมว่าวิธีนี้ใช้ได้กับการลบบางสิ่งเท่านั้น แต่อาจจบลงด้วยการวนซ้ำไม่รู้จบหากการดำเนินการทำให้เกิดการแทรกซ้ำในสถานที่ที่จะเข้ามาในภายหลัง ....
Yunnosch

@Yunnosch นอกเหนือจากอันตรายสำหรับการวนซ้ำไม่สิ้นสุดไม่มีปัญหาในการแทรกตัววนซ้ำหลังตัววนซ้ำปัจจุบัน Iterators std::setมีความเสถียรใน อาจจำเป็นต้องพิจารณากรณีเส้นขอบที่แทรกตัววนซ้ำใหม่ไว้ด้านหลังตัวลบโดยตรง - มันจะถูกข้ามหลังจากใส่ในลูป
Scheff

3
ใน C ++ 17 คุณสามารถextractโหนดปรับเปลี่ยนคีย์และส่งคืนกลับไปที่การตั้งค่า มันจะมีประสิทธิภาพมากขึ้นเนื่องจากจะหลีกเลี่ยงการจัดสรรที่ไม่จำเป็น
Daniel Langr

คุณช่วยอธิบายได้ไหมว่า "มันจะถูกข้ามไปหลังจากใส่ในลูป" ฉันคิดว่าฉันไม่เข้าใจประเด็นของคุณ
Yunnosch

2
std::setปัญหาก็คือว่ามูลค่าขององค์ประกอบที่หักอาจจะเป็นเช่นเดียวกับหนึ่งในค่าที่ยังที่ยังไม่ได้อยู่ใน เนื่องจากคุณไม่สามารถมีองค์ประกอบเดียวกันสองครั้งการแทรกจะปล่อยให้std::setไม่เปลี่ยนแปลงและคุณจะสูญเสียองค์ประกอบในภายหลัง พิจารณาเช่นชุดการป้อนข้อมูล: ด้วย{10, 20, 30} add = 10
ComicSansMS


5

คุณไม่สามารถเปลี่ยนแปลงองค์ประกอบของstd::setการออกแบบได้ ดู

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

เนื่องจากทั้ง iterator และ const_iterator เป็นตัววนซ้ำคงที่ (และในความเป็นจริงอาจเป็นชนิดเดียวกัน) จึงไม่สามารถกลายพันธุ์องค์ประกอบของคอนเทนเนอร์ผ่านตัววนซ้ำที่ส่งกลับโดยฟังก์ชันสมาชิกใด ๆ เหล่านี้

นั่นเป็นเพราะชุดจะเรียง หากคุณกลายพันธุ์องค์ประกอบในคอลเลกชันที่เรียงลำดับคอลเลกชันจะต้องเรียงอีกครั้งซึ่งแน่นอนที่สุด แต่ไม่ใช่วิธี C ++

ตัวเลือกของคุณคือ:

  1. ใช้คอลเล็กชันประเภทอื่น (ไม่เรียงลำดับ)
  2. สร้างชุดใหม่และเติมด้วยองค์ประกอบที่แก้ไข
  3. ลบองค์ประกอบออกจากstd::setปรับเปลี่ยนจากนั้นใส่อีกครั้ง (ไม่ใช่ความคิดที่ดีถ้าคุณต้องการแก้ไขทุกองค์ประกอบ)

4

std::setมักจะนำมาใช้เป็นตัวเองสมดุลต้นไม้ไบนารีใน STL *itคือค่าขององค์ประกอบที่ใช้ในการสั่งซื้อต้นไม้ หากเป็นไปได้ที่จะแก้ไขคำสั่งจะไม่ถูกต้องดังนั้นจึงไม่สามารถทำได้

หากคุณต้องการอัปเดตองค์ประกอบคุณจะต้องค้นหาองค์ประกอบนั้นในชุดลบออกแล้วใส่ค่าที่อัปเดตขององค์ประกอบนั้น แต่เนื่องจากคุณต้องอัปเดตค่าขององค์ประกอบทั้งหมดคุณจึงต้องลบและแทรกองค์ประกอบทั้งหมดทีละตัว

มันเป็นไปได้ที่จะทำในหนึ่งเดียวสำหรับวงที่จัดsub > 0ไว้ให้ S.erase(pos)ลบ iterator ที่ตำแหน่งposและส่งคืนตำแหน่งต่อไปนี้ หากsub > 0ค่าที่อัปเดตซึ่งคุณจะแทรกจะมาก่อนค่าที่ iterator ใหม่ในทรี แต่ถ้าsub <= 0ค่าที่อัปเดตจะมาหลังจากค่าที่ iterator ใหม่ในทรีและด้วยเหตุนี้คุณจะสิ้นสุดใน วนไม่มีสิ้นสุด.

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.