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;
}
aJ.
sumber
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.

Steve Folly
sumber
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
});
Iron Savior
sumber
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)
}
1800 INFORMASI
sumber
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.

partha biswas
sumber
2

Inilah beberapa solusi elegan.

for (auto it = map.begin(); it != map.end();)
{   
    (SomeCondition) ? map.erase(it++) : (++it);
}
Akar Mandrake
sumber
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.

piotr
sumber
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.

Vincent
sumber
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().

pengguna109134
sumber
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.

Greg Domjan
sumber
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);
aJ.
sumber
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.

Tadeusz Kopec
sumber
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;
    }
 }
voltento.dll
sumber