c++boostboost-icl

Boost ICL map that replaces values in intervals?


Boost.ICL's interval_map has two kinds of behaviors: += and insert. Both are useful in different context. The first adds up values in common intersections of two existing intervals. The second simply introduces the new value only in previously unassigned intervals (in previously assigned intervals the value is kept).

However I need a behavior that is subtly different, such that, in the example below instead of getting the undesired interval map (1.,2.)->1 , (2.5,3.)->3, (3.,5.)->2 I get instead the desired (1.,2.)->1 , (2.5,5.)->3.

That is, that new inserted values replace old values? How do I declare interval_map to get that replacing behavior?

#include<boost/icl/interval_map.hpp>
int main(){
    boost::icl::interval_map<double, int> joined_map;
    joined_map.insert( std::make_pair(
        boost::icl::interval<double>::open(1., 2.),
        1
    ));
    joined_map.insert( std::make_pair(
        boost::icl::interval<double>::open(3., 5.),
        2
    ));
    joined_map.insert( std::make_pair(
        boost::icl::interval<double>::open(2.5, 5.),
        3
    )); // this line doesn't replace the old value 2, it keeps it.
}

Bonus: Is that what boost::icl::map is supposed to do? How do I use it?


EDIT 1: This is a more explicit and simplified sample code using C++11

#include<boost/icl/interval_map.hpp>
#include<iostream>

namespace icl = boost::icl;
using interval = icl::interval<double>;

int main(){
    icl::interval_map<double, int> joined_map;

    joined_map.insert({interval::open(1., 2.), 1});
    joined_map.insert({interval::open(3., 5.), 2});
    joined_map.insert({interval::open(2.5, 5.), 3}); 
    // ^^^^ this line doesn't replace the old value 2! it keeps it.
    for(auto e: joined_map) std::cout << e.first <<' '<< e.second <<'\n';
    // prints: (1,2) 1 \\ (2.5,3] 3 \\ (3,5) 2
    // desired: (1,2) 1 \\ (2.5,5] 3  // value 2 gone
}

EDIT 2: Complete solution based on @JorgeBellon's answer:

#include<boost/icl/interval_map.hpp>
#include<iostream>

namespace icl = boost::icl;

template <class Type>
struct inplace_replace{// : icl::identity_based_inplace_combine<Type>{
    using first_argument_type = Type;
    void operator()(Type& object, Type const& operand) const{object = operand;}
};

using interval = icl::interval<double>;

int main(){

    icl::interval_map<
        double, int,
        icl::partial_enricher,   // Unmapped intervals have unkown value;
                                 // store identity values
        std::less             ,  // Comparator
        inplace_replace     //,  // Combination operator // IMPORTANT!!
    //  icl::inplace_erasure//,  // Extraction operator
    //  closed_interval<unsigned, std::less> // Interval type
    > joined_map;
    joined_map.add({interval::open(1. , 2.), 1}); // or joined_map+=std::make_pair(...)
    joined_map.add({interval::open(3. , 5.), 2}); // IMPORTANT: USE add, NOT insert!!
    joined_map.add({interval::open(2.5, 5.), 3}); 
    // ^^^^ this line now replaces the old value 2
    for(auto e: joined_map) std::cout << e.first <<' '<< e.second <<'\n';
    // prints: (1,2) 1 \\ (2.5,5] 3  // value 2 gone
}

Solution

  • One of the interval map's template parameters is the combination operator type. The typical example consists on map using std::set or similar as a value and then it uses the addition or the identity (keep the existing value) as operations.

    Since the overwrite example is not there by default, you can create yours and pass it to the map yourself:

    #include <boost/icl/interval_map.hpp>
    
    using namespace boost::icl;
    
    // interval_map combiner functor: assigns new value if key exists
    template <class Type>
    struct inplace_replace : identity_based_inplace_combine<Type> {
      void operator()(Type &object, const Type &operand) const {
        object = operand;
      }
    };
    
    template<>
    inline std::string unary_template_to_string<inplace_replace>::apply() {
      return "=";
    }
    
    // When adding, if interval exists, replaces value.
    // When subtracting, if interval exists, removes value.
    using ival_map =
        interval_map<unsigned,         // Key
                     unsigned,         // Value
                     partial_enricher, // Unmapped intervals have unkown value; store identity values
                     std::less,        // Comparator
                     inplace_replace,  // Combination operator
                     inplace_erasure,  // Extraction operator
                     >;
    

    See complete example: https://ideone.com/C49bDM

    Edit: deriving from identity_based_inplace_combine<Type> (boost/icl/functors.hpp) does the following:

    It is not mandatory to use it if your map is partial_enricher or total_enricher, since in this case the map will contain entries for any values, including identity values. You will need it for the absorber types, since the map needs to know whether it can drop the interval or not in that case.

    An alternative:

    // interval_map combiner functor: assigns new value if key exists
    template <class Type>
    struct inplace_replace {
      typedef void result_type;
      typedef Type& first_argument_type;
      typedef const Type& second_argument_type;
    
      void operator()(Type &object, const Type &operand) const {
        object = operand;
      }
    };
    

    Note: older boost ICL implementations derive from std::binary_function instead of using these typedefs. Unfortunately this is deprecated in C++11 and removed in C++17, so I would try not to use it in your own code. The latest versions implement the functors like the snippet above.

    Same example: https://ideone.com/lMLEDw