3

I have a map, filled with values:

std::map<std::tuple<int, int, double>, double> Cache;

When the size of map is greater than 100, I want to delete/erase only the map elements whose values in the key tuple are all more than 10. Here is how it can be done with while and if:

if (Cache.size() > 100) {
    auto it = Cache.begin();
    while (it != Cache.end()) {
        auto myCache = it->first;
        auto actualX = std::get<0>(myCache);
        auto actualY = std::get<1>(myCache);
        auto actualZ = std::get<2>(myCache);
        if (abs(actualX) > 10 || abs(actualY) > 10) || abs(actualZ) > 10)) {
            Cache.erase(it++);
        } else {
            it++;
        }
    }
}

How one can implement the same behavior using find_if (or other function) and lambda function?

Update2

After going through the wikipedia link provided in the comment section i have implemented the following code, but its giving an compiler error :(

Cache.erase(std::remove_if(Cache.begin(),
                                 Cache.end(),
                                 [=](auto &T) -> bool {
                                     auto myCache = T->first;
                                     auto actualX = std::get<0>(myCache);
                                     auto actualY = std::get<1>(myCache);
                                     return std::abs(actualX) > 10|| std::abs(actualY) > 10 || std::abs(actualZ) > 10;
                                 }),
                  Cache.end());

update 3

The above cannot be used with map ;(

  • 2
    `Cache.erase(it++);` is wrong, it should be `it = Cache.erase(it);`. – Quentin Mar 16 '18 at 13:28
  • 1
    Unfortunately `std::map` does not support `std::remove_if` so I think loop is a simplest solution here – Slava Mar 16 '18 at 13:29
  • @Quentin it is not wrong, it is an alternative – Slava Mar 16 '18 at 13:30
  • @Slava hmm. Is the increment strictly sequenced before the function call? – Quentin Mar 16 '18 at 13:31
  • 1
    @Quentin Yes. All function parameters are evaluated before the function is entered. – NathanOliver Mar 16 '18 at 13:32
  • @NathanOliver alright then. I'm leaving this for the next people to fall for it. But that begs the question, why do `erase` functions return the next iterator then? – Quentin Mar 16 '18 at 13:35
  • @Slava : I guess using find_if i could implement the condition as lambda function and the returned iterator can be then deleted. –  Mar 16 '18 at 13:35
  • It would use `const auto &myCache = it->first;` though for 2 ints and one double it could be not necessary. – Slava Mar 16 '18 at 13:35
  • 1
    Anyone here mentioned the Erase-remove idiom? https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom – TuanDT Mar 16 '18 at 13:36
  • @Quentin `erase` didn't used to and this was the one line solution pre C++11. Now that it returns the next iterator there isn't a need to use it but you still can. – NathanOliver Mar 16 '18 at 13:37
  • @vinaycool yea that possible but I do not see much improvement. Actually let me try – Slava Mar 16 '18 at 13:37
  • @TuanDT Yep, and you can't use it on associative containers – NathanOliver Mar 16 '18 at 13:37
  • 2
    I know its not answering the question but it's a really bad idea to use a double in a key – virgesmith Mar 16 '18 at 13:40
  • 1
    If you missed the second comment in this section, please read the documentation of [`std::remove_if`](http://en.cppreference.com/w/cpp/algorithm/remove) where in the notes it is stated: *"These algorithms cannot be used with associative containers such as std::set and std::map because ForwardIt does not dereference to a MoveAssignable type (the keys in these containers are not modifiable)"*. Have you tried any of the answers you already got? – Bob__ Mar 16 '18 at 14:42

4 Answers4

1

You can use std::find_if with predicate:

auto pred = []( const auto &t ) { 
    return std::abs( std::get<0>( t. ) ) > 10 || 
           std::abs( std::get<1>( t ) ) > 10 || 
           std::fabs( std::get<2>( t ) ) > 10.0 ); };

for( auto it = Cache.begin(); true; it = Cache.erase( it ) ) {
     it = std::find_if( it, Cache.end(), pred );
     if( it == Cache.end() ) break;
}
Slava
  • 43,454
  • 1
  • 47
  • 90
0

Something like following should work

namespace myUtility {

template< typename C, typename P >
  void custom_erase_if( C& items, const P& predicate ) {
    for( auto it = items.begin(); it != items.end(); ) {
      if( predicate(*it) ) it = items.erase(it);
      else ++it;
    }
  }
}

Then,

  myUtility::custom_erase_if( Cache, [](const auto& x) {
     auto myCache = x.first;
            auto actualX = std::get<0>(myCache);
            auto actualY = std::get<1>(myCache);
            auto actualZ = std::get<2>(myCache);
            return ( std::abs(actualX) > 10 || 
                    std::abs(actualY) > 10  || 
                    std::abs(actualZ) > 10) ) ; 
    } );
P0W
  • 46,614
  • 9
  • 72
  • 119
0

What you should do is arrange the map so that the elements you want are at the back of the map.

struct over_10 {
  bool operator()( std::tuple<int, int, double> const& t) const {
    return
      (std::get<0>(t)>10 || std::get<0>(t)<-10) // min int doesn't like abs
   && (std::get<1>(t)>10 || std::get<1>(t)<-10)
   && fabs(std::get<2>(t))>10.0;
  }
};
template<class Split>
struct split_order {
  template<class Lhs, class Rhs>
  bool operator()( Lhs const& lhs, Rhs const& rhs ) const {
    auto split = Split{};
    return std::tie( split(lhs), lhs ) < std::tie( split(rhs), rhs );
  }
};
using split_on_10_order = split_order<over_10>;

what this does is it splits the sorting so that things that pass the over_10 test are all ordered last. Everything is ordered sensibly within each "split".

Now:

std::map<std::tuple<int, int, double>, double, split_on_10_order> Cache;

auto it = Cache.lower_bound(
  std::make_tuple(
    std::numeric_limits<int>::min(),
    std::numeric_limits<int>::min(),
    std::numeric_limits<double>::min()
  )
);

Cache.erase( it, Cache.end() );

and done.

This takes about O(lg n) time to find the split, then O(m) to delete the m elements that should be deleted.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Unfortunately, as pointed by Slava, std::remove_if can't be used with maps.

I think your solution (a loop to erase the unwanted elements) isn't bad, but if you want to use the algorithm library, the best that come in my mint is use std::copy_if() to move the elements you want preserve in a temporary map and following swap the two maps.

I mean... something as

   if ( Cache.size() > 100 )
    {
      std::map<std::tuple<int, int, double>, double> CacheTmp;

      std::copy_if(std::make_move_iterator(Cache.begin()),
                   std::make_move_iterator(Cache.end()),
                   std::inserter(CacheTmp, CacheTmp.end()),
                   [] (auto const & p)
                     { return (std::abs(std::get<0>(p.first)) <= 10)
                           && (std::abs(std::get<1>(p.first)) <= 10)
                           && (std::abs(std::get<2>(p.first)) <= 10.0); });

      Cache.swap(CacheTmp);
    }

Unfortunately that auto parameter for lambda functions are available only starting from C++14, so if you need of a C++11 solution, instead of auto const & p, your lambda should receive a std::pair<td::tuple<int, int, double>, double> const & p

max66
  • 65,235
  • 10
  • 71
  • 111