remove_if tương đương cho std :: map


118

Tôi đang cố gắng xóa một loạt các yếu tố khỏi bản đồ dựa trên điều kiện cụ thể. Làm cách nào để thực hiện điều đó bằng thuật toán STL?

Ban đầu tôi nghĩ đến việc sử dụng remove_ifnhưng không thể vì remove_if không hoạt động cho vùng chứa liên kết.

Có bất kỳ thuật toán tương đương "remove_if" nào hoạt động cho bản đồ không?

Như một lựa chọn đơn giản, tôi nghĩ đến việc lặp lại bản đồ và xóa. Nhưng việc lặp lại bản đồ và xóa có phải là một tùy chọn an toàn không? (Vì các trình lặp sẽ không hợp lệ sau khi xóa)

Tôi đã sử dụng ví dụ sau:

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

Ý bạn là gì mà remove_if không hoạt động?
dirkgently

Tôi không thể sử dụng remove_if để tìm một phần tử trong bản đồ, phải không? Nó đã đưa ra một lỗi thời gian biên dịch. Tui bỏ lỡ điều gì vậy?
aJ.

Không - nó không hoạt động vì remove_if hoạt động bằng cách sắp xếp lại một chuỗi, di chuyển các phần tử không đạt điều kiện về cuối. Do đó, nó hoạt động trên T [n], nhưng không hoạt động trên bản đồ <T, U>.
MSalters

2
Với C + 11, bạn có thể sử dụng for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}để giảm bớt sự lộn xộn. Nghỉ ngơi là như những người khác đã nói. Câu hỏi này đã giúp tôi tiết kiệm được một vài sợi tóc vừa rồi ;-)
Atul Kumar

Câu trả lời:


111

Hầu hết.

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

Những gì bạn có ban đầu sẽ tăng trình lặp lên hai lần nếu bạn đã xóa một phần tử khỏi nó; bạn có thể bỏ qua các phần tử cần được xóa.

Đây là một thuật toán phổ biến mà tôi đã thấy được sử dụng và ghi lại ở nhiều nơi.

[EDIT] Bạn nói đúng rằng trình vòng lặp bị vô hiệu sau khi xóa, nhưng chỉ có trình vòng lặp tham chiếu đến phần tử bị xóa, các trình vòng lặp khác vẫn hợp lệ. Do đó sử dụng iter++trong erase()cuộc gọi.


4
Tôi bối rối; tại sao bạn lại sử dụng for (; ...;) thay vì while (...)? Ngoài ra, mặc dù điều này có thể hoạt động, nhưng .erase không trả về một trình lặp của cái tiếp theo? Vì vậy, có vẻ như blog if (Some Condition) phải là iter = aMap.erase (iter) để tương thích nhất. Có lẽ tôi đang thiếu một cái gì đó? Tôi thiếu kinh nghiệm mà một số bạn có.
taxilian

86
Lưu ý, trong C ++ 11 tất cả các vùng chứa kết hợp, bao gồm map, trả về trình lặp tiếp theo từ erase(iter). Nó sạch sẽ hơn nhiều để làm iter = erase( iter ).
Potatoswatter

10
@taxilian (trễ năm) while () hoặc for () sẽ hoạt động, nhưng về mặt ngữ nghĩa, mọi người thường sử dụng for () để lặp qua một phạm vi đã biết và while () cho một số vòng lặp không xác định. Vì phạm vi đã biết trong trường hợp này (từ đầu, đến endIter ), for () sẽ không phải là một lựa chọn bất thường và có lẽ sẽ phổ biến hơn. Nhưng một lần nữa, cả hai đều có thể chấp nhận được.
Jamin Grey

4
@taxilian Quan trọng hơn: với 'for', bạn có thể có định nghĩa trình vòng lặp của mình BÊN TRONG phạm vi của vòng lặp, vì vậy nó không gây rối với phần còn lại của chương trình của bạn.
Sanchises

1
@athos Câu hỏi được diễn đạt bằng giọng bị động, "nên dùng." Không có khuyến nghị chung. Tôi nghĩ nhận xét cuối cùng của tôi là cách thẳng thắn nhất. Nó liên quan đến hai bản sao của biến trình lặp, điều này làm mất một chút hiệu quả như ai đó đã chỉ ra ở đây. Đó là cuộc gọi của bạn những gì thích hợp cho bạn.
Potatoswatter

75

delete_if cho std :: map (và các vùng chứa khác)

Tôi sử dụng mẫu sau cho điều này.

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

Thao tác này sẽ không trả lại bất cứ thứ gì, nhưng nó sẽ xóa các mục khỏi bản đồ std ::.

Ví dụ sử dụng:

// '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 */;
});

Ví dụ thứ hai (cho phép bạn chuyển vào một giá trị thử nghiệm):

// '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 Cảm ơn - tôi luôn có vẻ kỳ lạ khi cái này chưa tồn tại std. Tôi hiểu tại sao nó không phải là thành viên của std::map, nhưng tôi nghĩ một cái gì đó giống như nó nên có trong thư viện tiêu chuẩn.
Iron Savior

3
Sẽ được thêm vào trong C ++ 20 chostd::map và những người khác.
Roi Danton


3

Tôi nhận tài liệu này từ tài liệu tham khảo SGI STL tuyệt vời :

Bản đồ có thuộc tính quan trọng là việc chèn một phần tử mới vào bản đồ không làm mất hiệu lực của các trình vòng lặp trỏ đến các phần tử hiện có. Việc xóa một phần tử khỏi bản đồ cũng không làm mất hiệu lực của bất kỳ trình vòng lặp nào, tất nhiên, ngoại trừ các trình vòng lặp thực sự trỏ đến phần tử đang bị xóa.

Vì vậy, trình lặp mà bạn có đang trỏ đến phần tử cần xóa tất nhiên sẽ bị vô hiệu. Làm điều gì đó như sau:

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

3
Điều này không khác với mã gốc. iter ++ tăng trình vòng lặp sau đó trả về một trình vòng lặp trỏ vào phần tử trước gia số.
Steve Folly

Nhưng nó sẽ không bị vô hiệu vì sau đó chúng tôi sẽ xóa ở vị trí ở đây
1800 THÔNG TIN

@ 1800INFORMATION: nhập một lệnh gọi hàm là một điểm trình tự, hiệu ứng phụ tăng dần được đánh giá trước khi eraseđược gọi. Vì vậy, chúng thực sự là tương đương. Tuy nhiên, tôi thực sự muốn phiên bản của bạn hơn phiên bản gốc.
peterchen

Nó hoạt động cho mảng hoặc vectơ, nhưng sẽ gây ra kết quả không mong muốn trong bản đồ stl.
hunter_tech

2

Mã ban đầu chỉ có một vấn đề:

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

Ở đây giá trị iternày được tăng lên một lần trong vòng lặp for và một lần nữa trong quá trình xóa, có thể sẽ kết thúc trong một số vòng lặp vô hạn.


2

Đây là một số giải pháp thanh lịch.

for (auto it = map.begin(); it != map.end();)
{   
    (SomeCondition) ? map.erase(it++) : (++it);
}

1

Từ ghi chú cuối cùng của:

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

Một vùng chứa liên kết cặp không thể cung cấp trình vòng lặp có thể thay đổi (như được định nghĩa trong các yêu cầu Trình lặp tầm thường), vì kiểu giá trị của trình lặp có thể thay đổi phải là Có thể gán và cặp không thể Chuyển nhượng được. Tuy nhiên, một Vùng chứa liên kết cặp có thể cung cấp các trình vòng lặp không hoàn toàn không đổi: các trình vòng lặp sao cho biểu thức (* i) .second = d là hợp lệ.


1

Đầu tiên

Bản đồ có thuộc tính quan trọng là việc chèn một phần tử mới vào bản đồ không làm mất hiệu lực của các trình vòng lặp trỏ đến các phần tử hiện có. Việc xóa một phần tử khỏi bản đồ cũng không làm mất hiệu lực của bất kỳ trình vòng lặp nào, tất nhiên, ngoại trừ các trình vòng lặp thực sự trỏ đến phần tử đang bị xóa.

Thứ hai, mã sau đây là tốt

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

Khi gọi một hàm, các tham số được đánh giá trước khi gọi hàm đó.

Vì vậy, khi iter ++ được đánh giá trước khi gọi xóa, toán tử ++ của trình vòng lặp sẽ trả về mục hiện tại và sẽ trỏ đến mục tiếp theo sau cuộc gọi.


1

IMHO không có remove_if()tương đương.
Bạn không thể sắp xếp lại bản đồ.
Vì vậy, remove_if()không thể đặt các cặp sở thích của bạn ở cuối mà bạn có thể gọi erase().


Điều đó thực sự đáng tiếc.
allyourcode

1

Dựa trên câu trả lời của Iron Savior Đối với những người muốn cung cấp một phạm vi nhiều hơn dọc theo các dòng của trình vòng lặp lấy chức năng std.

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

Tò mò nếu có cách nào đó để mất các ContainerTmục và lấy nó từ trình lặp.


1
"Các số nhận dạng bắt đầu bằng dấu gạch dưới theo sau là chữ hoa được dành riêng cho mọi cách sử dụng bởi quá trình triển khai."
YSC

0

Câu trả lời của Steve Folly tôi cảm thấy hiệu quả hơn.

Đây là một giải pháp dễ dàng nhưng kém hiệu quả hơn :

Giải pháp sử dụng remove_copy_ifđể sao chép các giá trị mà chúng ta muốn vào một vùng chứa mới, sau đó hoán đổi nội dung của vùng chứa ban đầu với nội dung của vùng chứa mới:

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
Điều đó có vẻ không hiệu quả.
allyourcode

0

Nếu bạn muốn xóa tất cả các phần tử có khóa lớn hơn 2, thì cách tốt nhất là

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

Tuy nhiên, chỉ hoạt động cho phạm vi, không cho bất kỳ vị ngữ nào.


0

Tôi sử dụng như thế này

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
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.