วิธีการโทรลบด้วยตัววนซ้ำย้อนกลับ


181

ฉันพยายามทำสิ่งนี้:

for ( std::list< Cursor::Enum >::reverse_iterator i = m_CursorStack.rbegin(); i != m_CursorStack.rend(); ++i )
{
    if ( *i == pCursor )
    {
        m_CursorStack.erase( i );
        break;
    }
}

อย่างไรก็ตามการลบจะใช้ตัววนซ้ำและไม่ใช่ตัววนซ้ำแบบย้อนกลับ มีวิธีในการแปลง reverse iterator เป็น iterator ปกติหรือวิธีอื่นในการลบองค์ประกอบนี้จากรายการ?


17
นอกจากนี้เมื่อเขียนลูปแบบนี้อย่าคำนวณซ้ำตัววนซ้ำอย่างที่คุณทำที่นี่ด้วยi != m_CursorStack.rend()ซ้ำ i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;แต่เขียน นั่นคือเริ่มต้นตัววนซ้ำที่คุณสามารถเก็บไว้เพื่อเปรียบเทียบซ้ำ - สมมติว่าตำแหน่งสุดท้ายจะไม่เปลี่ยนแปลงตามผลข้างเคียงของร่างกายลูปของคุณ
seh

สำหรับฉันดูเหมือนว่าคำถามที่ชัดเจนที่นี่จะเป็นสาเหตุที่คุณทำสิ่งนี้ทั้งหมด คุณได้อะไรจากการย้อนกลับรายการ คุณไม่ได้รับอะไรจากการเขียนโค้ดนี้ด้วยตัวเองแทนการใช้std::remove?
โลงศพเจอร์รี่

และตัววนซ้ำในรายการ std :: ยังคงใช้ได้เมื่อมีการเพิ่มขึ้นหลังจากองค์ประกอบที่มันอ้างถึงถูกลบไปแล้ว?
464 Steve Jessop

3
ฉันต้องการลบ 1 องค์ประกอบเท่านั้นดังนั้น 'break;' การใช้ 'remove' จะกำจัดสิ่งที่ตรงกันนั้นใช้เวลานานกว่าและไม่ทำสิ่งที่ฉันต้องการ องค์ประกอบที่ฉันต้องการลบในกรณีนี้มักจะเป็นจุดสิ้นสุดของรายการหรือใกล้เคียงกับมันมากที่สุดดังนั้นการวนซ้ำในทางกลับกันก็รวดเร็วและเหมาะสมกับปัญหามากขึ้น
0xC0DEFACE

4
stackoverflow.com/a/2160581/12386สิ่งนี้บอกว่านักออกแบบไม่ได้กำหนดการใช้งานเพราะคุณเป็นผู้ใช้ที่ไม่ควรรู้หรือสนใจ แต่ @seh ด้านบนคาดหวังให้เรารู้อย่างน่าอัศจรรย์ว่าเพิ่งจะคำนวณ () และมีราคาแพง
stu

คำตอบ:


181

หลังจากการวิจัยและทดสอบเพิ่มเติมฉันพบวิธีแก้ปัญหา เห็นได้ชัดว่าเป็นไปตามมาตรฐาน [24.4.1 / 1] ความสัมพันธ์ระหว่าง i.base () และ i คือ:

&*(reverse_iterator(i)) == &*(i - 1)

(จากบทความดร. ดอบส์ ):

ข้อความแสดงแทน

ดังนั้นคุณจำเป็นต้องใช้การชดเชยเมื่อได้รับฐาน () ดังนั้นการแก้ปัญหาคือ:

m_CursorStack.erase( --(i.base()) );

แก้ไข

การอัปเดตสำหรับ C ++ 11

reverse_iterator iไม่เปลี่ยนแปลง:

m_CursorStack.erase( std::next(i).base() );

reverse_iterator iเป็นขั้นสูง:

std::advance(i, 1);
m_CursorStack.erase( i.base() );

ฉันพบว่าวิธีนี้ชัดเจนกว่าโซลูชันก่อนหน้านี้มาก ใช้สิ่งที่คุณต้องการ


27
คุณควรจดบันทึกบทความที่คุณอ้างถึงอีกหน่อยเพื่อให้เป็นแบบพกพาการแสดงออกควรจะเป็นm_CursorStack.erase( (++i).base())(ผู้ชายการทำสิ่งนี้ด้วยตัววนซ้ำแบบย้อนกลับทำให้ฉันปวดหัว ... ) ควรสังเกตว่าบทความ DDJ นั้นรวมอยู่ในหนังสือ "Effective STL" ของเมเยอร์
ไมเคิลเสี้ยน

8
ฉันพบว่าแผนภาพนั้นสับสนมากกว่ามีประโยชน์ ตั้งแต่ rbegin, ri และ rend ต่างก็ชี้ไปที่องค์ประกอบทางด้านขวาของสิ่งที่พวกมันถูกวาดเพื่อชี้ไปที่ แผนภาพจะแสดงองค์ประกอบที่คุณจะเข้าถึงหากคุณเป็นองค์ประกอบ*นั้น แต่เรากำลังพูดถึงองค์ประกอบที่คุณจะชี้ให้เห็นหากคุณbaseเป็นองค์ประกอบที่อยู่ทางขวา ฉันไม่ใช่แฟนตัวยงของ--(i.base())หรือ(++i).base()วิธีแก้ปัญหาตั้งแต่พวกเขากลายพันธุ์ iterator ฉันชอบแบบ(i+1).base()ไหนดี
mgiuca

4
iterators ย้อนกลับเป็นผู้โกหก .. เมื่อ deferenced การย้อนกลับ iterator ส่งกลับองค์ประกอบก่อนที่มันจะ ดูที่นี่
bobobobo

4
เพื่อให้ชัดเจนอย่างยิ่งเทคนิคนี้ยังคงไม่สามารถใช้เป็นปกติสำหรับลูป (โดยที่ตัววนซ้ำจะเพิ่มขึ้นตามปกติ) ดูstackoverflow.com/questions/37005449/…
logidelic

1
m_CursorStack.erase ((++ i) .base ()) ดูเหมือนว่าจะเป็นปัญหาถ้า ++ ฉันพาคุณผ่านองค์ประกอบสุดท้าย คุณสามารถโทรลบเมื่อสิ้นสุด () ได้ไหม
stu

16

โปรดทราบว่าm_CursorStack.erase( (++i).base())อาจมีปัญหาหากใช้ในการforวนซ้ำ (ดูคำถามเดิม) เพราะมันเปลี่ยนค่าของฉัน การแสดงออกที่ถูกต้องคือm_CursorStack.erase((i+1).base())


4
คุณต้องสร้างสำเนาของตัววนซ้ำและทำiterator j = i ; ++jเพราะi+1ไม่ทำงานบนตัววนซ้ำ แต่นั่นเป็นความคิดที่ถูกต้อง
bobobobo

3
@bobobobo คุณสามารถใช้m_CursorStack.erase(boost::next(i).base())กับ Boost หรือใน C ++ 11m_CursorStack.erase(std::next(i).base())
alfC

12

... หรือวิธีอื่นในการลบองค์ประกอบนี้ออกจากรายการ

สิ่งนี้ต้องการ-std=c++11แฟล็ก (สำหรับauto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}

Works เสน่ห์ :)
รังเจสัน

@GaetanoMendola: ทำไมล่ะ
slashmais

3
ใครรับประกันได้ว่ามีการสั่งซื้อตัววนซ้ำในรายการ
Gaetano Mendola

7

ขำ ๆ ว่ายังไม่มีวิธีแก้ไขที่ถูกต้องในหน้านี้ ดังนั้นต่อไปนี้คือสิ่งที่ถูกต้อง:

ในกรณีของตัววนซ้ำไปข้างหน้าการแก้ปัญหาจะไปข้างหน้า:

std::list< int >::iterator i = myList.begin();
while ( ; i != myList.end(); ) {
  if ( *i == to_delete ) {
    i = myList.erase( i );
  } else {
    ++i;
  } 
}

ในกรณีของตัววนซ้ำแบบย้อนกลับคุณต้องทำสิ่งเดียวกัน:

std::list< int >::reverse_iterator i = myList.rbegin();
while ( ; i != myList.rend(); ) {
  if ( *i == to_delete ) {
    i = decltype(i)(myList.erase( std::next(i).base() ));
  } else {
    ++i;
  } 
}

หมายเหตุ:

  • คุณสามารถสร้าง a reverse_iteratorจากตัววนซ้ำ
  • คุณสามารถใช้ค่าส่งคืนเป็น std::list::erase

รหัสนี้ใช้งานได้ แต่โปรดอธิบายว่าเพราะเหตุใดจึงใช้งานต่อไปและวิธีที่ปลอดภัยในการส่งตัววนซ้ำไปข้างหน้าเป็นตัววนกลับแบบย้อนกลับโดยไม่มีการยุบโลก
Lefteris E

1
@LefterisE นั่นไม่ใช่นักแสดง มันสร้างตัววนซ้ำย้อนกลับใหม่ออกจากอินเทอร์ นี่เป็นตัวสร้างปกติของตัววนซ้ำแบบย้อนกลับ
ŠimonTóth

3

ขณะที่ใช้reverse_iterator's base()วิธีการและ decrementing ผลที่ได้ทำงานที่นี่ก็น่าสังเกตว่าreverse_iterators ไม่ได้รับสถานะเดียวกันเป็นปกติiterators โดยทั่วไปคุณควรชอบs (s) iteratorถึงปกติreverse_iterator( s) const_iteratorและconst_reverse_iterators (s) ด้วยเหตุผลที่แม่นยำเช่นนี้ ดูวารสาร Doctor Dobbs 'สำหรับการสนทนาเชิงลึกว่าทำไม


3
typedef std::map<size_t, some_class*> TMap;
TMap Map;
.......

for( TMap::const_reverse_iterator It = Map.rbegin(), end = Map.rend(); It != end; It++ )
{
    TMap::const_iterator Obsolete = It.base();   // conversion into const_iterator
    It++;
    Map.erase( Obsolete );
    It--;
}

3

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

std::set<int> set{1,2,3,4,5};

for (auto itr = set.rbegin(); itr != set.rend(); )
{    
    if (*itr == 3)
    {
        auto it = set.erase(--itr.base());
        itr = std::reverse_iterator(it);            
    }
    else
        ++itr;
}

2

หากคุณไม่จำเป็นต้องลบทุกอย่างตามที่คุณไปพร้อม ๆ กันเพื่อแก้ไขปัญหาคุณสามารถใช้สำนวนการลบได้:

m_CursorStack.erase(std::remove(m_CursorStack.begin(), m_CursorStack.end(), pCursor), m_CursorStack.end());

std::removeสลับไอเท็มทั้งหมดในคอนเทนเนอร์ที่ตรงpCursorกับส่วนท้ายและส่งคืนตัววนซ้ำไปยังไอเท็มแรกที่จับคู่ จากนั้นการeraseใช้ช่วงจะลบออกจากการแข่งขันครั้งแรกและไปที่จุดสิ้นสุด ลำดับขององค์ประกอบที่ไม่ตรงกันจะถูกรักษาไว้

สิ่งนี้อาจได้ผลเร็วกว่าสำหรับคุณหากคุณกำลังใช้ std::vectorซึ่งการลบเนื้อหาตรงกลางอาจเกี่ยวข้องกับการคัดลอกหรือย้ายจำนวนมาก

หรือแน่นอนคำตอบข้างต้นที่อธิบายการใช้งานreverse_iterator::base()นั้นน่าสนใจและคุ้มค่าที่จะรู้เพื่อแก้ไขปัญหาที่ระบุไว้แน่นอนฉันขอยืนยันว่าstd::removeเหมาะสมกว่า


1

แค่ต้องการชี้แจงบางอย่าง: ในความคิดเห็นข้างต้นและคำตอบเวอร์ชันพกพาสำหรับการลบถูกกล่าวถึงเป็น (++ i) .base () อย่างไรก็ตามหากฉันไม่มีบางสิ่งบางอย่างคำสั่งที่ถูกต้องคือ (++ ri) .base () หมายความว่าคุณ 'เพิ่ม' reverse_iterator (ไม่ใช่ตัววนซ้ำ)

ฉันพบว่าจำเป็นต้องทำสิ่งที่คล้ายกันเมื่อวานและโพสต์นี้มีประโยชน์ ขอบคุณทุกคน


0

เพื่อเติมเต็มคำตอบของผู้อื่นและเพราะฉันสะดุดกับคำถามนี้ในขณะที่ค้นหาเกี่ยวกับ std :: string โดยไม่ประสบความสำเร็จมากนี่คือการตอบสนองกับการใช้ std :: string, std :: string :: erase และ std :: reverse_iterator

ปัญหาของฉันคือการลบชื่อไฟล์ภาพจากสตริงชื่อไฟล์ที่สมบูรณ์ แต่เดิมมันถูกแก้ไขด้วย std :: string :: find_last_of แต่ฉันค้นคว้าวิธีอื่นด้วย std :: reverse_iterator

std::string haystack("\\\\UNC\\complete\\file\\path.exe");
auto&& it = std::find_if( std::rbegin(haystack), std::rend(haystack), []( char ch){ return ch == '\\'; } );
auto&& it2 = std::string::iterator( std::begin( haystack ) + std::distance(it, std::rend(haystack)) );
haystack.erase(it2, std::end(haystack));
std::cout << haystack;  ////// prints: '\\UNC\complete\file\'

วิธีนี้ใช้อัลกอริทึมตัววนซ้ำและส่วนหัวของสตริง


0

reverse iterator ค่อนข้างใช้งานยาก ดังนั้นเพียงใช้ตัววนซ้ำทั่วไป 'r' มันเริ่มจากองค์ประกอบสุดท้าย เมื่อพบสิ่งที่จะลบ ลบออกและส่งคืนตัววนซ้ำถัดไป เช่นเมื่อลบองค์ประกอบที่ 3 มันจะชี้องค์ประกอบที่ 4 ปัจจุบัน และใหม่ 3 ดังนั้นควรลดลง 1 เพื่อเลื่อนไปทางซ้าย

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.