remove_if setara dengan std :: map


118

Saya mencoba menghapus berbagai elemen dari peta berdasarkan kondisi tertentu. Bagaimana cara melakukannya menggunakan algoritma STL?

Awalnya saya berpikir untuk menggunakan remove_iftetapi tidak mungkin karena remove_if tidak berfungsi untuk wadah asosiatif.

Apakah ada algoritme setara "remove_if" yang berfungsi untuk peta?

Sebagai opsi sederhana, saya berpikir untuk mengulang melalui peta dan menghapus. Tetapi apakah perulangan melalui peta dan menghapus opsi yang aman? (Karena iterator menjadi tidak valid setelah dihapus)

Saya menggunakan contoh berikut:

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

Apa maksud Anda bahwa remove_if tidak berfungsi?
dirkgently

Saya tidak dapat menggunakan remove_if untuk menemukan elemen di peta, bukan? Ini memberi kesalahan waktu kompilasi. Apakah saya melewatkan sesuatu?
aJ.

Tidak - ini tidak berfungsi karena remove_if bekerja dengan menyusun ulang urutan, memindahkan elemen yang gagal dalam kondisi menjelang akhir. Oleh karena itu ia bekerja pada T [n], tetapi tidak pada peta <T, U>.
MSalters

2
Dengan C + 11, Anda dapat menggunakannya for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}untuk mengurangi kekacauan. Istirahat seperti yang dikatakan orang lain. Pertanyaan ini menyelamatkan saya dari beberapa masalah sekarang ;-)
Atul Kumar

Jawaban:


111

Hampir.

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

Apa yang Anda semula akan menambah iterator dua kali jika Anda benar-benar menghapus elemen darinya; Anda berpotensi melewatkan elemen yang perlu dihapus.

Ini adalah algoritma umum yang pernah saya lihat digunakan dan didokumentasikan di banyak tempat.

[EDIT] Anda benar bahwa iterator tidak valid setelah penghapusan, tetapi hanya iterator yang mereferensikan elemen yang dihapus, iterator lain masih valid. Karenanya menggunakan iter++dalam erase()panggilan.


4
Saya bingung; mengapa Anda menggunakan for (; ...;) daripada while (...)? Selain itu, meskipun ini mungkin berhasil, bukankah .erase mengembalikan iterator dari yang berikutnya? Jadi sepertinya blog if (Some Condition) harus iter = aMap.erase (iter) agar paling kompatibel. Mungkin saya melewatkan sesuatu? Saya kurang pengalaman yang beberapa dari Anda miliki.
taxilian

86
Catatan, di C ++ 11 semua wadah asosiatif, termasuk map, kembalikan iterator berikutnya dari erase(iter). Jauh lebih bersih untuk dilakukan iter = erase( iter ).
Potatoswatter

10
@taxilian (tahun terlambat) sementara () atau untuk () akan berfungsi, tetapi secara semantik, orang sering menggunakan for () untuk iterasi pada rentang yang diketahui, dan while () untuk jumlah loop yang tidak diketahui. Karena range diketahui dalam kasus ini (dari awal, hingga endIter ), for () bukanlah pilihan yang tidak biasa, dan mungkin akan lebih umum. Tapi sekali lagi, keduanya bisa diterima.
Jamin Grey

4
@taxilian Lebih penting lagi: dengan 'for', Anda dapat memiliki definisi iterator DI DALAM cakupan loop, sehingga tidak mengacaukan sisa program Anda.
Sanchises

1
@athos Pertanyaannya diutarakan dengan kalimat pasif, "disarankan." Tidak ada rekomendasi universal. Saya pikir komentar terakhir saya adalah cara yang paling mudah. Ini melibatkan dua salinan variabel iterator, yang kehilangan sedikit efisiensi seperti yang ditunjukkan di sini. Itu panggilan Anda apa yang sesuai untuk Anda.
Potatoswatter

75

erase_if untuk std :: map (dan penampung lainnya)

Saya menggunakan template berikut untuk hal ini.

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

Ini tidak akan mengembalikan apa pun, tetapi akan menghapus item dari std :: map.

Contoh penggunaan:

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

Contoh kedua (memungkinkan Anda untuk meneruskan nilai tes):

// '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 Terima kasih - selalu tampak aneh bagi saya bahwa ini belum ada std. Saya mengerti mengapa itu bukan anggota std::map, tetapi saya pikir sesuatu seperti itu harus ada di perpustakaan standar.
Iron Savior

3
Akan ditambahkan dalam C ++ 20 untukstd::map dan lainnya.
Roi Danton


3

Dokumentasi ini saya dapatkan dari referensi SGI STL yang sangat baik :

Peta memiliki properti penting sehingga memasukkan elemen baru ke dalam peta tidak membatalkan iterator yang mengarah ke elemen yang ada. Menghapus elemen dari peta juga tidak membatalkan iterator apa pun, kecuali, tentu saja, untuk iterator yang benar-benar mengarah ke elemen yang sedang dihapus.

Jadi, iterator yang Anda miliki yang mengarah ke elemen yang akan dihapus tentu saja tidak valid. Lakukan sesuatu seperti ini:

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

3
Ini tidak berbeda dengan kode aslinya. iter ++ menaikkan iterator lalu mengembalikan iterator yang menunjuk ke elemen sebelum kenaikan.
Steve Folly

Tetapi iter tidak akan dibatalkan karena kami kemudian menghapus pada posisi di sini
1800 INFORMATION

@ 1800INFORMATION: memasuki panggilan fungsi adalah titik urutan, efek samping kenaikan dievaluasi sebelum erasedipanggil. Jadi mereka memang setara. Namun, saya lebih memilih versi Anda daripada yang asli.
peterchen

Ini berfungsi untuk array atau vektor, tetapi akan menyebabkan hasil yang tidak terduga di stl map.
hunter_tech

2

Kode asli hanya memiliki satu masalah:

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

Di sini, iterbertambah sekali dalam perulangan for dan waktu lain dalam penghapusan, yang mungkin akan berakhir dalam beberapa perulangan tak terbatas.


2

Inilah beberapa solusi elegan.

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

1

Dari catatan bawah:

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

Pair Associative Container tidak dapat menyediakan iterator yang dapat diubah (seperti yang didefinisikan dalam persyaratan Trivial Iterator), karena tipe nilai dari iterator yang dapat berubah harus dapat ditetapkan, dan pasangan tidak dapat ditetapkan. Namun, Pair Associative Container dapat menyediakan iterator yang tidak sepenuhnya konstan: iterator sedemikian rupa sehingga ekspresi (* i) .second = d valid.


1

Pertama

Peta memiliki properti penting sehingga memasukkan elemen baru ke dalam peta tidak membatalkan iterator yang mengarah ke elemen yang ada. Menghapus elemen dari peta juga tidak membatalkan iterator apa pun, kecuali, tentu saja, untuk iterator yang benar-benar mengarah ke elemen yang sedang dihapus.

Kedua, kode berikut ini bagus

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

Saat memanggil suatu fungsi, parameter dievaluasi sebelum panggilan ke fungsi itu.

Jadi, ketika iter ++ dievaluasi sebelum panggilan untuk dihapus, operator ++ dari iterator akan mengembalikan item saat ini dan akan menunjuk ke item berikutnya setelah panggilan.


1

IMHO tidak ada yang remove_if()setara.
Anda tidak dapat menyusun ulang peta.
Jadi remove_if()tidak bisa menempatkan pasangan Anda di akhir yang dapat Anda panggil erase().


Sangat disayangkan.
allyourcode

1

Berdasarkan jawaban Iron Savior Bagi mereka yang ingin memberikan lebih banyak rentang di sepanjang garis iterator pengambilan fungsional 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;
    }
}

Penasaran apakah ada cara untuk kehilangan ContainerTitem dan mendapatkannya dari iterator.


1
"Pengenal yang dimulai dengan garis bawah diikuti dengan huruf besar disediakan untuk semua penggunaan oleh implementasi."
YSC

0

Jawaban Steve Folly saya rasa lebih efisien.

Berikut adalah solusi lain yang mudah tetapi kurang efisien :

Solusi yang digunakan remove_copy_ifuntuk menyalin nilai yang kita inginkan ke dalam wadah baru, lalu menukar isi wadah asli dengan yang baru:

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
Itu sepertinya tidak efisien.
allyourcode

0

Jika Anda ingin menghapus semua elemen dengan kunci lebih besar dari 2, maka cara terbaik adalah

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

Hanya berfungsi untuk rentang, bukan untuk predikat apa pun.


0

Saya menggunakan seperti ini

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.