Na pierwszy ogień poszedł std::remove_if(), niestety problemem jest tu w jaki sposób mapy przechowują swoje elementy. std::map<K,V>::value_type jest parą std::pair<const K, V>. std::remove_if() przesuwa elementy do usunięcia w taki sposób, że zostają one nadpisane. Ponieważ nie można przypisywać do const, std::remove_if() nie nadaje się do zastosowania na mapach. Wykorzystałem go jednak w zwykłej metodzie nib::erase_if() [linia 21], gdyż świetnie nadaje się dla zwykłych kontenerów. Wątek na stackoveflow, który traktuje o tym problemie:
Z tego co udało mi się wyszukać innym, częstym rozwiązaniem jest skorzystanie z std::remove_copy_if(). Tym razem wszystkie elementy, które nie spełniają naszego kryterium są kopiowane do nowej mapy. Jeżeli mapa jest sporych rozmiarów, a elementów do usunięcia jest stosunkowo dużo, to minusem rozwiązaniem będzie niepotrzebne chwilowe zwiększenie zapotrzebowanie na pamięć. Działanie tego algorytmu zostało zawarte w metodzie nib_test::erase_if() [linia 47].
Ostatnie i najbardziej preferowane rozwiązanie - nib::erase_if [linia 27], to napisanie własnego algorytmu, który podejmie się tego zadania. Szkoda, że nie udało się niczego takiego zaadoptować (albo jeszcze nie znalazłem) w bibliotece standardowej albo w innych popularnych bibliotekach (np. boost).
Po odnalezieniu elementów o zadanej wartości zostaną one usunięty za pomocą metody std::map::erase(). Dobra wiadomość jest taka, że std::map::erase(), unieważnia jedynie referencje i iteratory do usuwanego elementu, pozostałe referencje i iteratory w mapie są nadal ważne: Dwa wątki traktujące o tym rozwiązaniu:
Jak informuje autor, jednego z rozwiązań, w przypadku skorzystania z zapisu "erase(it++)", standard gwarantuje, że wszystkie wyrażenia argumentów, będą wykonane przed wywołaniem funkcji. Prosta inkrementacja zostanie wykonany przed wywołaniem funkcji std::map::erase(), a do niej zostanie przekazana wartość jeszcze niezainkrementowana.
#include <map>
#include <vector>
#include <algorithm>
#include <iostream>
template <typename Key, typename Value>
std::ostream& operator<<(std::ostream& out, const std::pair<Key, Value>& p) {
return out << "[" << p.first << "]" << "=" << p.second;
}
template <typename Container>
void print_container(const Container& container) {
for(const auto& v : container)
std::cout << v << std::endl;
}
namespace nib
{
template <typename Container, typename Predicate>
void erase_if(Container& container, const Predicate& predicate) {
container.erase(std::remove_if(begin(container), end(container), predicate),
end(container));
}
template <typename Key, typename Value, typename Predicate>
void erase_if(std::map<Key, Value>& dict, const Predicate& predicate) {
auto it = begin(dict);
while(it != end(dict)) {
if (predicate(*it)) {
// Standard zapewnia, ze it++ zostanie obliczone, zanim samo "it"
// zostanie przekazane do funkcji
dict.erase(it++);
}
else {
++it;
}
}
}
} // namespace nib
namespace nib_test
{
template <typename Key, typename Value, typename Predicate>
void erase_if(std::map<Key, Value>& dict, const Predicate& predicate) {
typedef typename std::remove_reference<decltype(dict)>::type Map;
Map tmp;
std::remove_copy_if(begin(dict), end(dict),
std::inserter(tmp, begin(tmp)),
predicate);
dict = tmp;
}
} // namespace nib_test
int main() {
std::vector<int> vec { 1, 2, 1, 4, 1, 5 };
auto val_to_erase = [] (int& v) { return v == 1; };
nib::erase_if(vec, val_to_erase);
print_container(vec);
typedef std::map<int, std::string> Dict;
Dict dict { {1, "a"}, {2, "b"}, {3, "c"}, {7, "b"} };
auto val_to_erase_in_map = [] (typename Dict::value_type& v) { return v.second == "b"; };
nib::erase_if(dict, val_to_erase_in_map);
// nib_test::erase_if(dict, val_to_erase_in_map);
print_container(dict);
return 0;
}
Wyniki:2 4 5 [1]=a [3]=cAby się upewnić co do kosumpcji pamięci przez oba rozwiązania, przeprowadziłem prostą weryfikacją za pomocą valgrinda.
int main() {
typedef std::map<int, std::string> Dict;
Dict dict { {1, "a"}, {2, "b"}, {3, "c"}, {7, "b"} };
for(int i = 0; i < 10000; ++i)
dict.insert(std::make_pair(5000 + i, "x"));
// dict.emplace(5000 + i, "x"); - Nie dziala w GCC 4.7!!!
auto val_to_erase_in_map = [] (typename Dict::value_type& v) { return v.second == "b"; };
// nib::erase_if(dict, val_to_erase_in_map);
nib_test::erase_if(dict, val_to_erase_in_map);
print_container(dict);
return 0;
}
Wersja nib::erase_if(), odnotowała peak na poziomie 558272 bajtów, natomiast dla nib_test::erase_if() (korzystająca z std::remove_copy_if()) peak ten wynosił 874376 bajty.
Brak komentarzy:
Prześlij komentarz