ฉันจะลบรายการออกจากเวกเตอร์ stl ด้วยค่าที่แน่นอนได้อย่างไร


145

ฉันดูเอกสาร API สำหรับ stl vector และสังเกตว่าไม่มีวิธีในคลาสเวกเตอร์ที่อนุญาตให้ลบองค์ประกอบด้วยค่าที่แน่นอน ดูเหมือนว่าเป็นการดำเนินการทั่วไปและดูเหมือนแปลกที่ไม่มีวิธีการทำเช่นนี้


2
ฉันรู้ว่าฉันเคยพูดถึงเรื่องนี้มาหลายครั้งแล้ว แต่หนังสือSTL ที่มีประสิทธิภาพของ Scott Meyer ครอบคลุมการได้รับเหล่านี้อย่างชัดเจน
Rob Wells

1
ที่เกี่ยวข้อง: stackoverflow.com/questions/3385229/…
bobobobo

นี่อาจเป็นการอ่านที่น่าสนใจสำหรับคุณ: en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
sergiol

คำตอบ:


165

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

std::vector<int> vec;
// .. put in some values ..
int int_to_remove = n;
vec.erase(std::remove(vec.begin(), vec.end(), int_to_remove), vec.end());

3
ไม่นี้ทั้งหมดในหนึ่งเดียวคำสั่งรูปแบบขึ้นอยู่กับลำดับที่คอมไพเลอร์จะประเมินข้อโต้แย้งหรือvec.end()รับประกันว่าจะเหมือนกันในด้านของการเรียกร้องให้ทั้งสองstd::remove? ดูเหมือนว่าฉันจะอ่านส่วนอื่น ๆ ของเว็บเช่นนี้มีความปลอดภัย แต่ควรระบุไว้อย่างชัดเจน
dmckee --- ผู้ดูแลอดีตลูกแมว

1
ทุกอย่างปกติดี. ผลลัพธ์ของการvec.end()ไม่จำเป็นต้องเหมือนกัน; มันเพียงแค่ต้องถูกต้อง (ซึ่งเป็น)
Jim Buck

8
vec.end()ไม่จำเป็นต้องเหมือนกัน แต่ก็ใช้ได้เพราะstd::removeไม่เปลี่ยนแปลง หากมีการเปลี่ยนแปลง (และทำให้ค่าเก่าเป็นโมฆะ) ก็จะมีปัญหา: ลำดับของการประเมินค่าพารามิเตอร์นั้นไม่ได้ระบุไว้ดังนั้นคุณจะไม่ทราบว่าตัวที่สองvec.end()นั้นยังใช้งานได้ตามเวลาที่ใช้ เหตุผลเหมือนกันง่ายstd::removeไม่เปลี่ยนขนาดของคอนเทนเนอร์มันแค่ย้ายเนื้อหาไปรอบ ๆ
Steve Jessop

2
ฉันพบคำถามนี้สำคัญเนื่องจากฉันมีปัญหาเดียวกัน แต่สตูดิโอภาพของฉันใช้เวลาstd::removeเพียงอาร์กิวเมนต์เดียวเท่านั้น const char *_Filenameกล่าวคือ ฉันต้องใช้วิธีใดในการโทร
วิกเตอร์

15
นั่นคือเวอร์ชันremoveที่ลบไฟล์ คุณต้องรวม<algorithm>เพื่อเข้าถึงเวอร์ชันของดีลremoveนั้นด้วยคอนเทนเนอร์
Jim Buck

64

หากคุณต้องการที่จะลบรายการต่อไปนี้จะเป็นบิตมีประสิทธิภาพมากขึ้น

std::vector<int> v;


auto it = std::find(v.begin(), v.end(), 5);
if(it != v.end())
    v.erase(it);

หรือคุณอาจหลีกเลี่ยงค่าใช้จ่ายในการเคลื่อนย้ายรายการหากคำสั่งซื้อไม่สำคัญกับคุณ:

std::vector<int> v;

auto it = std::find(v.begin(), v.end(), 5);

if (it != v.end()) {
  using std::swap;

  // swap the one to be removed with the last element
  // and remove the item at the end of the container
  // to prevent moving all items after '5' by one
  swap(*it, v.back());
  v.pop_back();
}

3
หมายเหตุสิ่งนี้จะไม่ลบรายการที่ซ้ำกันหากมีอยู่ขณะที่ std :: remove_if ใช้
Den-Jason

15

ใช้เมธอด global std :: remove พร้อมกับตัววนเริ่มต้นและตัวสิ้นสุดจากนั้นใช้ std :: vector.erase เพื่อลบองค์ประกอบออก

ลิงก์เอกสาร
std :: remove http://www.cppreference.com/cppalgorithm/remove.html
std :: vector.erase http://www.cppreference.com/cppvector/erase.html

std::vector<int> v;
v.push_back(1);
v.push_back(2);

//Vector should contain the elements 1, 2

//Find new end iterator
std::vector<int>::iterator newEnd = std::remove(v.begin(), v.end(), 1);

//Erase the "removed" elements.
v.erase(newEnd, v.end());

//Vector should now only contain 2

ขอบคุณ Jim Buck ที่ชี้ให้เห็นข้อผิดพลาดของฉัน


สิ่งนี้มีความสามารถในการป้องกันการเคลื่อนไหวขององค์ประกอบอื่น ๆ ในการลบองค์ประกอบที่อยู่ตรงกลางของเวกเตอร์ดังนั้นสิ่งนี้จึงเร็วขึ้น มันเลื่อนพวกมันไปยังจุดสิ้นสุดของเวกเตอร์ก่อนจากนั้นคุณก็สามารถทิ้งได้จากจุดสิ้นสุดของเวกเตอร์
Etherealone

5

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

หากคุณกำลังดำเนินการนี้อย่างเข้มข้นคุณควรพิจารณา std :: set แทนด้วยเหตุผลนี้


5

หากคุณมีเวกเตอร์ที่ไม่เรียงลำดับคุณสามารถสลับกับองค์ประกอบเวกเตอร์สุดท้ายresize()ได้

std::vector::erase()ด้วยภาชนะสั่งซื้อคุณจะดีที่สุดออกด้วย โปรดทราบว่ามีการstd::remove()กำหนดไว้ใน<algorithm>แต่จะไม่ทำการลบ (อ่านเอกสารอย่างละเอียด)



3

จากc ++ 20 :

แนะนำฟังก์ชั่นที่ไม่ใช่สมาชิกstd::eraseซึ่งจะนำเวกเตอร์และค่าที่จะลบออกเป็นอินพุต

อดีต:

std::vector<int> v = {90,80,70,60,50};
std::erase(v,50);

2
ไม่เพียงเท่านั้นมันมีการใช้งานมากเกินไปสำหรับภาชนะแต่ละอัน!
HolyBlackCat

... และใช้ประโยชน์จากคุณสมบัติเฉพาะของภาชนะเช่นmap::erase!
LF

2

ดูเพิ่มเติมstd :: remove_ifเพื่อให้สามารถใช้เพรดิเคต ...

นี่คือตัวอย่างจากลิงค์ด้านบน:

vector<int> V;
V.push_back(1);
V.push_back(4);
V.push_back(2);
V.push_back(8);
V.push_back(5);
V.push_back(7);

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 4 2 8 5 7"

vector<int>::iterator new_end = 
    remove_if(V.begin(), V.end(), 
              compose1(bind2nd(equal_to<int>(), 0),
                       bind2nd(modulus<int>(), 2)));
V.erase(new_end, V.end()); [1]

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 5 7".

2
โปรดเพิ่มรายละเอียดเพิ่มเติมในโพสต์นี้ เนื้อหาส่วนใหญ่มาจากลิงก์และจะหายไปหากลิงก์หยุดชะงัก
Mick MacCallum

สิ่งที่เกี่ยวกับความจริง bind2nd คือ - (คัดค้านใน C ++ 11) (ลบใน C ++ 17)
Idan Banani

0

ถ้าคุณต้องการที่จะทำมันโดยไม่ต้องรวมพิเศษใด ๆ :

vector<IComponent*> myComponents; //assume it has items in it already.
void RemoveComponent(IComponent* componentToRemove)
{
    IComponent* juggler;

    if (componentToRemove != NULL)
    {
        for (int currComponentIndex = 0; currComponentIndex < myComponents.size(); currComponentIndex++)
        {
            if (componentToRemove == myComponents[currComponentIndex])
            {
                //Since we don't care about order, swap with the last element, then delete it.
                juggler = myComponents[currComponentIndex];
                myComponents[currComponentIndex] = myComponents[myComponents.size() - 1];
                myComponents[myComponents.size() - 1] = juggler;

                //Remove it from memory and let the vector know too.
                myComponents.pop_back();
                delete juggler;
            }
        }
    }
}

0

มีสองวิธีที่คุณสามารถใช้เพื่อลบรายการโดยเฉพาะ ลองหาเวกเตอร์

std :: vector < int > v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(40);
v.push_back(50);

1) วิธีที่ไม่มีประสิทธิภาพ:ถึงแม้ว่ามันจะค่อนข้างมีประสิทธิภาพ แต่ไม่ใช่เพราะฟังก์ชั่นลบลบองค์ประกอบและเลื่อนองค์ประกอบทั้งหมดไปทางซ้าย 1 ดังนั้นความซับซ้อนจะเป็น O (n ^ 2)

std :: vector < int > :: iterator itr = v.begin();
int value = 40;
while ( itr != v.end() )
{
   if(*itr == value)
   { 
      v.erase(itr);
   }
   else
       ++itr;
}

2) วิธีที่มีประสิทธิภาพ (แนะนำ) : มันก็ยังเป็นที่รู้จักในฐานะERASE - สำนวนและนำออก

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

เอาท์พุทของอัลกอริทึมลบคือ:

10 20 30 50 40 50 

เนื่องจาก return type of remove เป็นตัววนซ้ำไปยังจุดสิ้นสุดใหม่ของช่วงนั้น

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val);

ตอนนี้ใช้ฟังก์ชั่นลบของเวกเตอร์เพื่อลบองค์ประกอบจากปลายใหม่ไปยังจุดสิ้นสุดเก่าของเวกเตอร์ มันต้องใช้เวลา O (1)

v.erase ( std :: remove (v.begin() , v.end() , element ) , v.end () );

ดังนั้นวิธีการนี้ทำงานใน O (n)


0

* * * *

ชุมชน C ++ ได้ยินคำขอของคุณแล้ว :)

* * * *

C ++ 20มอบวิธีที่ง่ายในการทำทันที มันง่ายเหมือน:

#include <vector>
...
vector<int> cnt{5, 0, 2, 8, 0, 7};
std::erase(cnt, 0);

คุณควรตรวจสอบมาตรฐาน :: ลบและมาตรฐาน :: erase_if

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

ถ้าคอมไพเลอร์ของคุณไม่สนับสนุน C ++ 20 คุณควรใช้สำนวนลบลบ :

#include <algorithm>
...
vec.erase(std::remove(vec.begin(), vec.end(), 0), vec.end());
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.