0

Consider the following program:

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

struct Value
{
    explicit Value(int v) : v(v), is_empty(false) {}
    Value() : v(0), is_empty(true) {}

    Value& operator+=(const Value& other)
    {
        v += other.v;
        return *this;
    }

    bool operator==(const Value& other) const { return is_empty == other.is_empty; }
    bool operator!=(const Value& other) const { return is_empty != other.is_empty; }

    int v;
    bool is_empty;
};

int main()
{
    boost::icl::interval_map<int, Value> m;
    m.add(std::make_pair(boost::icl::interval<int>::right_open(10, 20), Value(2)));
    m.add(std::make_pair(boost::icl::interval<int>::right_open(15, 30), Value(3)));
    std::cout << m.iterative_size() << '\n';
    std::cout << m.begin()->first.lower() << '\n';
    std::cout << m.begin()->first.upper() << '\n';
    std::cout << m.begin()->second.v << '\n';
}

The output is

1
10
30
2

Questons:

  • Why is the last line 2?
  • Isn't the interval map expected to add up the value, so that it is 5?
  • How to achieve the behavior that intervals are added and the value from += is preserved?

I want the following results:

{([10,20)->(2))+
      ([15,30)->(3))+ 
                       ([40,50)->(11))}
=
{([10,30)->(5))([40,50)->(11))}

That is, when intervals are added, they are merged, and the combined value is stored for entire merged interval.

There's may be no math sense in this operation, but this is what I need in my program. (Also, I don't to subtract intervals).

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79

2 Answers2

1

This is my own attempt on a pragmatic answer only on the last question:

How to achieve the behavior that intervals are added and the value from += is preserved?

I found another implementation, way simpler, but that can be easily customized to the behavior I want. It takes interval type as template parameter, and uses method join to join intervals, so I specialize it with my own interval type, which adds the value and on join sums the value:

#include <iostream>
#include "interval_tree.hpp"

using iv_base = lib_interval_tree::interval<int, lib_interval_tree::right_open>;
struct iv_t : iv_base
{
    int value;

    iv_t(const iv_base& base, int v)
        : iv_base(base)
        , value(v)
    {
    }

    iv_t(value_type low, value_type high, int v)
        : iv_base(low, high)
        , value(v)
    {
    }

    iv_t join(iv_t const& other) const
    {
        return iv_t(iv_base::join(other), value + other.value);
    }
};

using it_t = lib_interval_tree::interval_tree<iv_t>;

int main()
{
    it_t it;

    it.insert_overlap(iv_t( 10, 20, 2 ));
    it.insert_overlap(iv_t{ 15, 30, 3 });
    it.insert_overlap(iv_t{ 40, 50, 11 });

    for (decltype(auto) v : it)
    {
        std::cout << v.low() << ".." << v.high() << ":" << v.value << "\n";
    }
}

The output is:

10..30:5
40..50:11

Totally possible Boost.Icl can be customized for the same behavior, but it is more complex to understand if it is possible and how to do it.

sehe
  • 374,641
  • 47
  • 450
  • 633
Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • What is the output you get now? I don't understand what you expect. I'd expect `{([10,15)->2)([15,20)->5)([20,30)->3)}` from your question code. So with your "answer code" I'd expect `{([10,15)->2)([15,20)->5)([20,30)->3)([40,50)->11)}`. – sehe Feb 21 '22 at 23:06
  • If you link to where-ever `lib_interval_tree` came from I can maybe try to understand your requirements myself? – sehe Feb 22 '22 at 13:39
  • @sehe, https://github.com/5cript/interval-tree – Alex Guteniev Feb 22 '22 at 14:24
  • I added the output to your post; See however my [other comment](https://stackoverflow.com/questions/71206958/why-wouldnt-boosticlinterval-map-add-up-the-value/71214200?noredirect=1#comment125898529_71214200) – sehe Feb 22 '22 at 16:53
1

The problem is that

bool operator==(const Value& other) const { return is_empty == other.is_empty; }
bool operator!=(const Value& other) const { return is_empty != other.is_empty; }

make it so that ANY value is "identical". That makes the behaviour unspecified, and likely ends up merging any touching intervals and keeping the previous value (because it was "equal" according to your own operator==).

Let's have C++ generate a more correct comparison:

struct Value {
    explicit Value(int v) : value(v) {}
    Value() : is_empty(true) {}

    Value& operator+=(const Value& other) { value += other.value; return *this; }

    auto operator<=>(Value const&) const = default;

    bool is_empty = false;
    int  value    = 0;
};

Now you can Live On Compiler Explorer

#include <iostream>
#include <boost/icl/interval_map.hpp>
int main()
{
    namespace icl = boost::icl;
    using I = icl::interval<int>;
    using Value = int;

    auto const a = std::make_pair(I::right_open(10, 20), Value(2));
    auto const b = std::make_pair(I::right_open(15, 30), Value(3));
    auto const c = std::make_pair(I::right_open(40, 50), Value(11));

    icl::interval_map<int, Value> m;
    m.add(a);
    m.add(b);
    m.add(c);

    std::cout << m << "\n";

    m.subtract(a);

    std::cout << m << "\n";
}

Printing

{([10,15)->(2))([15,20)->(5))([20,30)->(3))([40,50)->(11))}

However, I'm struggling to understand what Value adds over optional<int> which, in fact is already the behaviour of the interval_map anyways:

int main()
{
    namespace icl = boost::icl;
    using I = icl::interval<int>;

    auto const a = std::make_pair(I::right_open(10, 20), 2);
    auto const b = std::make_pair(I::right_open(15, 30), 3);
    auto const c = std::make_pair(I::right_open(40, 50), 11);

    icl::interval_map<int, int> m;
    m.add(a);
    m.add(b);
    m.add(c);

    std::cout << m << "\n";

    m.subtract(a);

    std::cout << m << "\n";
}

Prints:

{([10,15)->2)([15,20)->5)([20,30)->3)([40,50)->11)}
{([15,30)->3)([40,50)->11)}

In fact, in some respects, your custom Value take seems "wrong" to me, evem with the fixed comparison:

Live On Compiler Explorer

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

struct Value {
    explicit Value(int v) : value(v) {}
    Value() : is_empty(true) {}

    Value& operator+=(const Value& other) { value += other.value; return *this; }
    Value& operator-=(const Value& other) { value -= other.value; return *this; }

    auto operator<=>(Value const&) const = default;

    bool is_empty = false;
    int  value    = 0;

    friend std::ostream& operator<<(std::ostream& os, Value const& v) {
        return v.is_empty ? os << "#EMPTY" : os << v.value;
    }
};

int main()
{
    namespace icl = boost::icl;
    using I = icl::interval<int>;

    auto const a = std::make_pair(I::right_open(10, 20), Value(2));
    auto const b = std::make_pair(I::right_open(15, 30), Value(3));
    auto const c = std::make_pair(I::right_open(40, 50), Value(11));

    icl::interval_map<int, Value> m;
    m.add(a);
    m.add(b);
    m.add(c);

    std::cout << m << "\n";

    m.subtract(a);

    std::cout << m << "\n";
}

Printing

{([10,15)->2)([15,20)->5)([20,30)->3)([40,50)->11)}
{([10,15)->0)([15,30)->3)([40,50)->11)}

Is [10,15)->0 really intended, desired?

sehe
  • 374,641
  • 47
  • 450
  • 633
  • if the answer to the last question is "yes", avoid more complexity: https://compiler-explorer.com/z/K4cqGr5eP – sehe Feb 21 '22 at 23:37
  • Unfortunately, this is not what I want, I want the intervals always added (never broken) and value always combined (so that entire merged interval contains the combined value) – Alex Guteniev Feb 22 '22 at 05:54
  • So e.g. `([5,10)->1) + ([0,100)->3)` should be `([0,100)->4)`? What if you have `{([0,5)->1), ([30,100)->7)} + ([4,40)->2)`? – sehe Feb 22 '22 at 13:38
  • yes, `{([0,5)->1), ([30,100)->7)} + ([4,40)->2)` will be `([0,100) -> 10)` – Alex Guteniev Feb 22 '22 at 14:26
  • 1
    Oh aha. Of all the possible options that was one I never considered. However, the approach shown in your answer ***doesn't do that***: https://godbolt.org/z/hz8c7d66q (includes copied inline). The best I can currently think of using Boost ICL: https://godbolt.org/z/s8vh97x3E – sehe Feb 22 '22 at 16:52
  • 1
    *thanks*! More for showing how to do this with ICL, than pointing out the bug. (The behavior I had was unexpected, but I could have found/fixed it myself, but pulling https://github.com/5cript/interval-tree have would require approval, and also it requires modification, as iterators from it are not bidi, only forward) – Alex Guteniev Feb 22 '22 at 18:24