Làm thế nào để xóa khỏi bản đồ trong khi lặp lại nó?


177

Làm cách nào để xóa bản đồ trong khi lặp lại? giống:

std::map<K, V> map;
for(auto i : map)
    if(needs_removing(i))
        // remove it from the map

Nếu tôi sử dụng map.erasenó sẽ làm mất hiệu lực các trình vòng lặp



Thậm chí tương tự hơn: stackoverflow.com/questions/800955/
Kẻ


Câu trả lời:


279

Các thành ngữ kết hợp-container xóa tiêu chuẩn:

for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
  if (must_delete)
  {
    m.erase(it++);    // or "it = m.erase(it)" since C++11
  }
  else
  {
    ++it;
  }
}

Lưu ý rằng chúng tôi thực sự muốn một forvòng lặp thông thường ở đây, vì chúng tôi đang sửa đổi chính container. Vòng lặp dựa trên phạm vi nên được dành riêng cho các tình huống mà chúng ta chỉ quan tâm đến các yếu tố. Cú pháp cho RBFL làm cho điều này rõ ràng bằng cách thậm chí không để lộ container bên trong thân vòng lặp.

Biên tập. Pre-C ++ 11, bạn không thể xóa const-iterators. Ở đó bạn sẽ phải nói:

for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }

Xóa một phần tử khỏi một thùng chứa không phải là mâu thuẫn với hằng số của phần tử. Bằng cách tương tự, nó luôn luôn là hoàn toàn hợp pháp đối với delete pnơi plà một con trỏ đến hằng số. Sự kiên định không ràng buộc suốt đời; giá trị const trong C ++ vẫn có thể dừng tồn tại.


1
"thậm chí không để lộ container bên trong thân vòng lặp", ý bạn là gì?
Dani

2
@Dani: Chà, tương phản điều này với việc xây dựng thế kỷ 20 for (int i = 0; i < v.size(); i++). Ở đây chúng ta phải nói v[i]bên trong vòng lặp, tức là chúng ta phải đề cập rõ ràng đến container. Mặt khác, RBFL giới thiệu biến vòng lặp có thể sử dụng trực tiếp làm giá trị và do đó, không có kiến ​​thức nào về container được yêu cầu bên trong vòng lặp. Đây là manh mối cho việc sử dụng RBFL dự định cho các vòng lặp mà không phải biết về container. Xóa là hoàn toàn ngược lại, trong đó tất cả là về container.
Kerrek SB

3
@skyhisi: Thật vậy. Đây là một trong những cách sử dụng hợp pháp của gia tăng sau: Gia tăng đầu tiênit để có được trình lặp tiếp theo, hợp lệ, và sau đó xóa cái cũ. Nó không hoạt động theo cách khác!
Kerrek SB

5
Tôi đã đọc ở đâu đó rằng trong C ++ 11, it = v.erase(it);bây giờ cũng hoạt động cho các bản đồ. Đó là, xóa () trên tất cả các yếu tố kết hợp bây giờ trả về trình lặp tiếp theo. Vì vậy, loại bùn cũ yêu cầu tăng sau ++ trong phần xóa (), không còn cần thiết nữa. Điều này (nếu đúng) là một điều tốt, vì bùn được dựa vào ma thuật ghi đè-tăng-sau-trong-chức năng-cuộc gọi, được "cố định" bởi những người duy trì newbie để tăng số lần gọi ra khỏi chức năng hoặc để hoán đổi nó đến một tiền đề "bởi vì đó chỉ là một thứ phong cách", v.v.
Dewi Morgan

3
Tại sao bạn gọi it++trong if else khối? Sẽ không đủ để gọi nó một lần sau những điều này?
nburk

25

Cá nhân tôi thích mẫu này rõ ràng hơn và đơn giản hơn, với chi phí của một biến phụ:

for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
  ++next_it;
  if (must_delete)
  {
    m.erase(it);
  }
}

Ưu điểm của phương pháp này:

  • bộ tăng vòng lặp for có ý nghĩa như một bộ tăng;
  • thao tác xóa là một thao tác xóa đơn giản, thay vì trộn lẫn với logic tăng dần;
  • sau dòng đầu tiên của thân vòng lặp, ý nghĩa của itnext_itvẫn được cố định trong suốt quá trình lặp, cho phép bạn dễ dàng thêm các câu lệnh bổ sung liên quan đến chúng mà không cần che đầu xem chúng có hoạt động như dự định không (trừ khi bạn không thể sử dụng itsau khi xóa nó) .

2
Tôi có thể nghĩ đến một lợi thế khác thực sự, nếu vòng lặp gọi vào mã xóa mục nhập đó được lặp đi lặp lại hoặc trước đó (và vòng lặp không biết về điều đó) thì nó sẽ hoạt động mà không gây hại gì. Hạn chế duy nhất là liệu thứ gì đó đang xóa đi những gì đang được chỉ ra bởi next_it hay người kế nhiệm. Một danh sách / bản đồ hoàn toàn bị xóa cũng có thể được kiểm tra.
Larswad

Câu trả lời này rất đơn giản và rõ ràng, ngay cả khi vòng lặp phức tạp hơn và có nhiều mức logic để quyết định có nên xóa hay không thực hiện các tác vụ khác nhau. Tuy nhiên, tôi đã đề xuất một chỉnh sửa để làm cho nó đơn giản hơn một chút. "next_it" có thể được đặt thành "it" trong init for để tránh lỗi chính tả và vì các câu lệnh init và lặp đều đặt nó và next_it thành cùng một giá trị, bạn không cần phải nói "next_it = it;" ở đầu vòng lặp.
cdgraham

1
Hãy ghi nhớ bất cứ ai sử dụng câu trả lời này: Bạn phải có "++ next_it" bên trong vòng lặp for và không có trong biểu thức lặp. Nếu bạn cố gắng di chuyển nó vào biểu thức lặp là "it = next_it ++", thì ở lần lặp cuối cùng, khi "nó" sẽ được đặt bằng "m.cend ()", bạn sẽ cố gắng lặp lại "next_it" quá khứ "m.cend ()", đó là sai lầm.
cdgraham

6

Tóm lại "Làm cách nào để tôi xóa khỏi bản đồ trong khi lặp lại nó?"

  • Với bản đồ cũ, bạn không thể
  • Với bản đồ mới ngụ ý: gần như @KerrekSB đề xuất. Nhưng có một số vấn đề cú pháp trong những gì ông đăng.

Từ bản đồ GCC impl (lưu ý GXX_EXPERIMENTAL_CXX0X ):

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *  @return An iterator pointing to the element immediately following
       *          @a position prior to the element being erased. If no such 
       *          element exists, end() is returned.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      iterator
      erase(iterator __position)
      { return _M_t.erase(__position); }
#else
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      void
      erase(iterator __position)
      { _M_t.erase(__position); }
#endif

Ví dụ với phong cách cũ và mới:

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>

using namespace std;
typedef map<int, int> t_myMap;
typedef vector<t_myMap::key_type>  t_myVec;

int main() {

    cout << "main() ENTRY" << endl;

    t_myMap mi;
    mi.insert(t_myMap::value_type(1,1));
    mi.insert(t_myMap::value_type(2,1));
    mi.insert(t_myMap::value_type(3,1));
    mi.insert(t_myMap::value_type(4,1));
    mi.insert(t_myMap::value_type(5,1));
    mi.insert(t_myMap::value_type(6,1));

    cout << "Init" << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    t_myVec markedForDeath;

    for (t_myMap::const_iterator it = mi.begin(); it != mi.end() ; it++)
        if (it->first > 2 && it->first < 5)
            markedForDeath.push_back(it->first);

    for(size_t i = 0; i < markedForDeath.size(); i++)
        // old erase, returns void...
        mi.erase(markedForDeath[i]);

    cout << "after old style erase of 3 & 4.." << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    for (auto it = mi.begin(); it != mi.end(); ) {
        if (it->first == 5)
            // new erase() that returns iter..
            it = mi.erase(it);
        else
            ++it;
    }

    cout << "after new style erase of 5" << endl;
    // new cend/cbegin and lambda..
    for_each(mi.cbegin(), mi.cend(), [](t_myMap::const_reference it){cout << '\t' << it.first << '-' << it.second << endl;});

    return 0;
}

in:

main() ENTRY
Init
        1-1
        2-1
        3-1
        4-1
        5-1
        6-1
after old style erase of 3 & 4..
        1-1
        2-1
        5-1
        6-1
after new style erase of 5
        1-1
        2-1
        6-1

Process returned 0 (0x0)   execution time : 0.021 s
Press any key to continue.

1
Tôi không hiểu Vấn đề là mi.erase(it++);gì?
lvella

1
@lvella xem op. "Nếu tôi sử dụng map.erase, nó sẽ làm mất hiệu lực các trình vòng lặp".
Kashyap

Phương pháp mới của bạn sẽ không hoạt động nếu sau khi xóa, bản đồ trở nên trống rỗng. Trong trường hợp đó, iterator sẽ bị vô hiệu. Vì vậy, ngay sau khi xóa, tốt hơn là chèn if(mi.empty()) break;.
Rahat Zaman

4

Bản nháp C ++ 20 chứa chức năng tiện lợi std::erase_if.

Vì vậy, bạn có thể sử dụng chức năng đó để làm điều đó như một lớp lót.

std::map<K, V> map_obj;
//calls needs_removing for each element and erases it, if true was reuturned
std::erase_if(map_obj,needs_removing);
//if you need to pass only part of the key/value pair
std::erase_if(map_obj,[](auto& kv){return needs_removing(kv.first);});

3

Khá buồn hả? Cách tôi thường làm là xây dựng một thùng chứa các vòng lặp thay vì xóa trong quá trình truyền tải. Sau đó lặp qua container và sử dụng map.erase ()

std::map<K,V> map;
std::list< std::map<K,V>::iterator > iteratorList;

for(auto i : map ){
    if ( needs_removing(i)){
        iteratorList.push_back(i);
    }
}
for(auto i : iteratorList){
    map.erase(*i)
}

Nhưng sau khi xóa một phần còn lại sẽ không hợp lệ
Dani


@Dani: Không có trong bản đồ. Xóa trong bản đồ chỉ làm mất hiệu lực iterator với mục bị xóa.
ChúBens

3

Giả sử C ++ 11, đây là phần thân vòng lặp một lớp, nếu điều này phù hợp với phong cách lập trình của bạn:

using Map = std::map<K,V>;
Map map;

// Erase members that satisfy needs_removing(itr)
for (Map::const_iterator itr = map.cbegin() ; itr != map.cend() ; )
  itr = needs_removing(itr) ? map.erase(itr) : std::next(itr);

Một vài thay đổi phong cách nhỏ khác:

  • Hiển thị loại khai báo ( Map::const_iterator) khi có thể / thuận tiện, qua sử dụng auto.
  • Sử dụng usingcho các loại mẫu, để làm cho các loại phụ trợ ( Map::const_iterator) dễ đọc / bảo trì hơn.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.