remove_if เทียบเท่าสำหรับ std :: map


118

ฉันพยายามลบองค์ประกอบต่างๆออกจากแผนที่ตามเงื่อนไขเฉพาะ ฉันจะทำได้อย่างไรโดยใช้อัลกอริทึม STL

เริ่มแรกฉันคิดว่าจะใช้ remove_ifแต่เป็นไปไม่ได้เนื่องจาก remove_if ใช้ไม่ได้กับคอนเทนเนอร์ที่เชื่อมโยง

มีอัลกอริทึมที่เทียบเท่า "remove_if" ที่ใช้ได้กับแผนที่หรือไม่

ในฐานะตัวเลือกง่ายๆฉันคิดว่าจะวนลูปผ่านแผนที่และลบ แต่กำลังวนรอบแผนที่และลบตัวเลือกที่ปลอดภัยหรือไม่ (เนื่องจากตัวทำซ้ำไม่ถูกต้องหลังจากลบ)

ฉันใช้ตัวอย่างต่อไปนี้:

bool predicate(const std::pair<int,std::string>& x)
{
    return x.first > 2;
}

int main(void) 
{

    std::map<int, std::string> aMap;

    aMap[2] = "two";
    aMap[3] = "three";
    aMap[4] = "four";
    aMap[5] = "five";
    aMap[6] = "six";

//      does not work, an error
//  std::remove_if(aMap.begin(), aMap.end(), predicate);

    std::map<int, std::string>::iterator iter = aMap.begin();
    std::map<int, std::string>::iterator endIter = aMap.end();

    for(; iter != endIter; ++iter)
    {
            if(Some Condition)
            {
                            // is it safe ?
                aMap.erase(iter++);
            }
    }

    return 0;
}

คุณหมายความว่าอย่างไรที่ remove_if ไม่ทำงาน
สม่ำเสมอ

ฉันไม่สามารถใช้ remove_if เพื่อค้นหาองค์ประกอบในแผนที่ได้ใช่ไหม มันทำให้เกิดข้อผิดพลาดเวลาคอมไพล์ ฉันพลาดอะไรไปรึเปล่า?
aJ.

ไม่ - มันใช้ไม่ได้เนื่องจาก remove_if ทำงานโดยการจัดลำดับลำดับใหม่ย้ายองค์ประกอบที่ล้มเหลวในเงื่อนไขไปสู่จุดสิ้นสุด ดังนั้นจึงทำงานบน T [n] แต่ไม่ใช่แผนที่ <T, U>
MSalters

2
ด้วย C + 11 คุณสามารถใช้for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}เพื่อลดความยุ่งเหยิง ส่วนที่เหลือเป็นไปตามที่คนอื่นกล่าว คำถามนี้ช่วยให้ฉันเพิ่งแยกผมบางส่วน ;-)
Atul Kumar

คำตอบ:


111

เกือบจะ

for(; iter != endIter; ) {
     if (Some Condition) {
          iter = aMap.erase(iter);
     } else {
          ++iter;
     }
}

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

นี่เป็นอัลกอริทึมทั่วไปที่ฉันเคยเห็นใช้และบันทึกไว้ในหลาย ๆ ที่

[แก้ไข] คุณถูกต้องที่ตัวทำซ้ำจะไม่ถูกต้องหลังจากลบไปแล้ว แต่มีเพียงตัวทำซ้ำที่อ้างถึงองค์ประกอบที่ถูกลบไปแล้วตัวทำซ้ำอื่น ๆ ยังคงใช้ได้ ดังนั้นการใช้iter++ในerase()โทร


4
ฉันสับสน; ทำไมคุณถึงใช้สำหรับ (; ... ;) แทน while (... )? นอกจากนี้ในขณะที่อาจใช้งานได้ไม่ได้. ลบกลับตัวทำซ้ำของรายการถัดไป? ดังนั้นดูเหมือนว่าบล็อก if (Some Condition) ควรเป็น iter = aMap.erase (iter) เพื่อให้เข้ากันได้มากที่สุด บางทีฉันอาจจะขาดอะไรไป? ฉันขาดประสบการณ์บางอย่างที่คุณมี
taxilian

86
หมายเหตุใน C ++ 11 ภาชนะบรรจุที่เชื่อมโยงทั้งหมดรวมทั้งmapผลตอบแทน iterator erase(iter)ถัดจาก iter = erase( iter )มันสะอาดมากจะทำอย่างไร
Potatoswatter

10
@taxilian (หลายปีที่ผ่านมา) ในขณะที่ () หรือ for () ใช้ได้ผล แต่ในทางความหมายผู้คนมักใช้สำหรับ () สำหรับการวนซ้ำในช่วงที่ทราบและ while () สำหรับจำนวนลูปที่ไม่รู้จัก เนื่องจากช่วงเป็นที่ทราบกันดีในกรณีนี้ (ตั้งแต่ต้นจนถึงendIter ) สำหรับ () ไม่ใช่ตัวเลือกที่ผิดปกติและอาจเป็นเรื่องปกติ แต่อีกครั้งทั้งสองจะเป็นที่ยอมรับ
Jamin Gray

4
@taxilian ที่สำคัญกว่านั้น: ด้วย 'for' คุณสามารถมีคำจำกัดความตัววนซ้ำของคุณภายในขอบเขตของการวนซ้ำดังนั้นมันจึงไม่ยุ่งกับส่วนที่เหลือของโปรแกรมของคุณ
Sanchises

1
@athos คำถามจะถูกเขียนด้วยน้ำเสียงแฝงว่า "ขอแนะนำ" ไม่มีคำแนะนำที่เป็นสากล ฉันคิดว่าความคิดเห็นสุดท้ายของฉันเป็นวิธีที่ตรงไปตรงมาที่สุด มันเกี่ยวข้องกับสำเนาของตัวแปรวนซ้ำสองชุดซึ่งจะสูญเสียประสิทธิภาพเล็กน้อยตามที่มีคนชี้ให้เห็นที่นี่ เป็นการโทรของคุณในสิ่งที่เหมาะสมสำหรับคุณ
Potatoswatter

75

erase_if สำหรับ std :: map (และคอนเทนเนอร์อื่น ๆ )

ฉันใช้เทมเพลตต่อไปนี้สำหรับสิ่งนี้

namespace stuff {
  template< typename ContainerT, typename PredicateT >
  void erase_if( ContainerT& items, const PredicateT& predicate ) {
    for( auto it = items.begin(); it != items.end(); ) {
      if( predicate(*it) ) it = items.erase(it);
      else ++it;
    }
  }
}

สิ่งนี้จะไม่ส่งคืนอะไรเลย แต่จะลบรายการออกจาก std :: map

ตัวอย่างการใช้งาน:

// 'container' could be a std::map
// 'item_type' is what you might store in your container
using stuff::erase_if;
erase_if(container, []( item_type& item ) {
  return /* insert appropriate test */;
});

ตัวอย่างที่สอง (อนุญาตให้คุณส่งผ่านค่าทดสอบ):

// 'test_value' is value that you might inject into your predicate.
// 'property' is just used to provide a stand-in test
using stuff::erase_if;
int test_value = 4;  // or use whatever appropriate type and value
erase_if(container, [&test_value]( item_type& item ) {
  return item.property < test_value;  // or whatever appropriate test
});

3
@CodeAngry ขอบคุณ - stdมันก็ดูเหมือนแปลกกับผมว่าเรื่องนี้ไม่ได้มีอยู่แล้วใน ฉันเข้าใจว่าเหตุใดจึงไม่ได้เป็นสมาชิกstd::mapแต่ฉันคิดว่าบางอย่างควรอยู่ในไลบรารีมาตรฐาน
Iron Savior

3
จะถูกเพิ่มในC ++ 20 สำหรับstd::mapและอื่น ๆ
ร้อยดอง


3

ฉันได้รับเอกสารนี้จากการอ้างอิง SGI STL ที่ยอดเยี่ยม :

แผนที่มีคุณสมบัติที่สำคัญที่การแทรกองค์ประกอบใหม่ลงในแผนที่ไม่ทำให้ตัวแสดงซ้ำที่ชี้ไปยังองค์ประกอบที่มีอยู่เป็นโมฆะ การลบองค์ประกอบออกจากแผนที่จะไม่ทำให้ตัวทำซ้ำใด ๆ เป็นโมฆะยกเว้นแน่นอนสำหรับตัวทำซ้ำที่ชี้ไปยังองค์ประกอบที่กำลังถูกลบ

ดังนั้นตัววนซ้ำที่คุณมีซึ่งชี้ไปที่องค์ประกอบที่จะลบแน่นอนว่าจะไม่ถูกต้อง ทำสิ่งนี้:

if (some condition)
{
  iterator here=iter++;
  aMap.erase(here)
}

3
นี่ไม่ต่างจากรหัสเดิม iter ++ เพิ่มตัววนซ้ำจากนั้นส่งคืนตัววนซ้ำที่ชี้ไปที่องค์ประกอบก่อนส่วนเพิ่ม
Steve Folly

แต่ iter จะไม่เป็นโมฆะเนื่องจากเราลบที่ตำแหน่งนี้
1800 ข้อมูล

@ 1800INFORMATION: การป้อนการเรียกใช้ฟังก์ชันเป็นจุดลำดับผลข้างเคียงที่เพิ่มขึ้นจะได้รับการประเมินก่อนที่จะeraseถูกเรียก ดังนั้นพวกเขาจึงเทียบเท่ากัน ถึงกระนั้นฉันขอแนะนำให้คุณใช้เวอร์ชันของคุณมากกว่าเวอร์ชันดั้งเดิม
peterchen

ใช้งานได้กับอาร์เรย์หรือเวกเตอร์ แต่จะทำให้เกิดผลลัพธ์ที่ไม่คาดคิดในแผนที่ stl
hunter_tech

2

รหัสเดิมมีเพียงปัญหาเดียว:

for(; iter != endIter; ++iter)
{
    if(Some Condition)
    {
        // is it safe ?
        aMap.erase(iter++);
    }
}

ที่นี่iterจะเพิ่มขึ้นหนึ่งครั้งในลูป for และอีกครั้งในการลบซึ่งอาจจะจบลงด้วยการวนซ้ำที่ไม่มีที่สิ้นสุด



1

จากบันทึกด้านล่างของ:

http://www.sgi.com/tech/stl/PairAssociativeContainer.html

Pair Associative Container ไม่สามารถจัดเตรียมตัวทำซ้ำที่เปลี่ยนแปลงได้ (ตามที่กำหนดไว้ในข้อกำหนด Trivial Iterator) เนื่องจากชนิดค่าของตัววนซ้ำที่เปลี่ยนแปลงได้ต้องเป็น Assignable และคู่ไม่สามารถกำหนดได้ อย่างไรก็ตาม Pair Associative Container สามารถจัดเตรียมตัววนซ้ำที่ไม่คงที่อย่างสมบูรณ์: ตัวทำซ้ำเพื่อให้นิพจน์ (* i) .second = d ถูกต้อง


1

เป็นครั้งแรก

แผนที่มีคุณสมบัติที่สำคัญที่การแทรกองค์ประกอบใหม่ลงในแผนที่ไม่ทำให้ตัวแสดงซ้ำที่ชี้ไปยังองค์ประกอบที่มีอยู่เป็นโมฆะ การลบองค์ประกอบออกจากแผนที่จะไม่ทำให้ตัวทำซ้ำใด ๆ เป็นโมฆะยกเว้นแน่นอนสำหรับตัวทำซ้ำที่ชี้ไปยังองค์ประกอบที่กำลังถูกลบ

ประการที่สองรหัสต่อไปนี้ดี

for(; iter != endIter; )
{
    if(Some Condition)
    {
        aMap.erase(iter++);
    }
    else
    {
        ++iter;
    }
}

เมื่อเรียกใช้ฟังก์ชันพารามิเตอร์จะได้รับการประเมินก่อนการเรียกใช้ฟังก์ชันนั้น

ดังนั้นเมื่อมีการประเมิน iter ++ ก่อนการเรียกให้ลบตัวดำเนินการ ++ ของตัวทำซ้ำจะส่งคืนรายการปัจจุบันและจะชี้ไปที่รายการถัดไปหลังจากการเรียก


1

IMHO ไม่มีทางremove_if()เทียบเท่า
คุณไม่สามารถจัดลำดับแผนที่ใหม่ได้
ดังนั้นremove_if()ไม่สามารถใส่คู่ที่คุณสนใจในตอนท้ายที่คุณสามารถโทรerase()ได้


นั่นเป็นเรื่องที่โชคร้ายจริงๆ
allyourcode

1

จากคำตอบของ Iron Saviorสำหรับผู้ที่ต้องการให้ช่วงเพิ่มเติมตามบรรทัดของการทำซ้ำฟังก์ชันมาตรฐาน

template< typename ContainerT, class FwdIt, class Pr >
void erase_if(ContainerT& items, FwdIt it, FwdIt Last, Pr Pred) {
    for (; it != Last; ) {
        if (Pred(*it)) it = items.erase(it);
        else ++it;
    }
}

อยากรู้ว่ามีวิธีใดบ้างที่จะสูญเสียContainerTไอเท็มและรับสิ่งนั้นจากตัววนซ้ำ


1
"ตัวระบุที่ขึ้นต้นด้วยขีดล่างตามด้วยอักษรตัวพิมพ์ใหญ่สงวนไว้สำหรับการใช้งานทั้งหมด"
YSC

0

คำตอบของ Steve Follyฉันรู้สึกว่ามีประสิทธิภาพมากขึ้น

นี่เป็นอีกหนึ่งวิธีแก้ปัญหาที่ง่าย แต่มีประสิทธิภาพน้อย :

โซลูชันนี้ใช้remove_copy_ifเพื่อคัดลอกค่าที่เราต้องการไปยังคอนเทนเนอร์ใหม่จากนั้นสลับเนื้อหาของคอนเทนเนอร์เดิมกับของใหม่:

std::map<int, std::string> aMap;

...
//Temporary map to hold the unremoved elements
std::map<int, std::string> aTempMap;

//copy unremoved values from aMap to aTempMap
std::remove_copy_if(aMap.begin(), aMap.end(), 
                    inserter(aTempMap, aTempMap.end()),
                    predicate);

//Swap the contents of aMap and aTempMap
aMap.swap(aTempMap);

2
ดูเหมือนไม่มีประสิทธิภาพ
allyourcode

0

หากคุณต้องการลบองค์ประกอบทั้งหมดด้วยคีย์ที่มากกว่า 2 วิธีที่ดีที่สุดคือ

map.erase(map.upper_bound(2), map.end());

ใช้ได้กับช่วงเท่านั้นไม่ใช่สำหรับเพรดิเคตใด ๆ


0

ฉันใช้แบบนี้

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.