c++boost-icl

Boost `interval_map` - how to customize aggregating on touch


The Boost ICL interval_set can join right-open intervals, which touch each other, during adding them to the set. For example, intervals [0,4) and [4,8) will be joined to become an interval [0,8).

This is more complicated for the interval_map - intervals, which touch each other and have different associated values, won't be joined:

#include <iostream>
#include <utility>

#include <boost/icl/interval_map.hpp>

namespace icl = boost::icl;

using IMap = icl::interval_map<int, int>;

int main()
{
  IMap m;
  m += std::make_pair(IMap::interval_type::right_open(0, 4), 1);
  m += std::make_pair(IMap::interval_type::right_open(4, 8), 2);
  std::cout << m << std::endl;
}

Output of this test program is below:

{([0,4)->1)([4,8)->2)}

I know how to customize the process of aggregating on overlap, however I need to customize another case - aggregating on touch. For example, if intervals touch each other and value of the left interval is equal to the value of the right interval minus 1, then intervals must be joined, and the resulting interval must have a value of the left interval. So, the program above should print:

{([0,8)->1)}

Is it possible to do that with currently available Boost ICL?

I can do what I want using weird manipulations with the interval_map, but I think it'd be cumbersome and non-efficient. I'd prefer to be pointed in right direction to use currently available ICL customizations, functors etc.


Solution

  • This is more complicated for the interval_map - intervals, which touch each other and have different associated values, won't be joined:

    There's no difference, really.

    I know how to customize the process of aggregating on overlap, however I need to customize another case - aggregating on touch.

    You seem to imply that

     m += std::make_pair(IMap::interval_type::right_open(4, 8), 2);
    

    will insert [4, 8) -> 2.

    That's simply not the case. It's a codomain combination operation, and the results depend on prior state of the map.

    Of course, you can write it:

    m.set({Ival::right_open(4, 8), 2});
    

    If you need to, you can query the preceding slot, so your operation might look like:

    // returns true if joined with preceding slot
    bool fill_slot(IMap& m, int from, int till, int value) {
        bool joined = false;
        auto slot = Ival::right_open(from, till);
    
        if (within(slot, m)) {
            // There is overlap, I don't know how  you want to handle this.
            // You can add some logic here.
        } else {
            auto preceding = m(from - 1);
    
            if (preceding && value == preceding + 1) {
                joined = true;
                value = preceding;
            }
        }
    
        m.set({slot, value});
        return joined;
    }
    

    Now you can write test cases like:

    int main() {
        {
            IMap m;
            fill_slot(m,  0,  4,  1);
            fill_slot(m,  4,  8,  2);
            std::cout << m << std::endl;
        }
        {
            IMap m;
            fill_slot(m,  0,  4,  1);
            fill_slot(m,  4,  8,  3);
            std::cout << m << std::endl;
        }
        {
            IMap m;
            fill_slot(m,  0,  4,  1);
            fill_slot(m,  5,  8,  2);
            std::cout << m << std::endl;
        }
    }
    

    And they print Live On Coliru

    {([0,8)->1)}
    {([0,4)->1)([4,8)->3)}
    {([0,4)->1)([5,8)->2)}