DEV Community

Iirq
Iirq

Posted on • Updated on

Value ranges or diapasons

Introduction

Every time in on every new job I encounter the same problem. There is no tool to work with diapasons or ranges. I don't know why but I forced to develop it again and again. For the sake of the future, I will write it once and will reuse. 

Solution

The simplest solution is:

//
// Simple diapason
//
struct ValueRange 
{
    double start = 0.0;
    double stop = 0.0;

    // returns distance |start, stop|
    double length() const { return stop - start; }
    // returns whether u inside this
    bool doesInclude(double u) const { return start <= u && u <= stop; }
    // returns corrected value (inserted into range, if it was outside)
    double included(double v) const;
};
Enter fullscreen mode Exit fullscreen mode

But somebody may want works with floats rather than doubles; So the code will change to:

//
// Simple template diapason 
//
template<typename T>
struct ValueRangeT
{
    using Type = T;

    // to be like std::
    using value_type = T; // used type

    T start{};
    T stop{};

    // returns distance |start, stop|
    T length() const { return stop - start; }
    // returns whether u inside this
    bool doesInclude(T u) const { return start <= u && u <= stop; }
    // returns corrected value (inserted into range, if it was outside)
    T included(T v) const;
};
Enter fullscreen mode Exit fullscreen mode

You will be warned that this ValueRange will work only with closed intervals [start, stop]. To solve this let's delegate comparison to the strategy defined later:

//
// ValueRangeImpl - general diapason from start to stop
// It maybe:
// -closed [start, stop],
// -open (start, stop),
// -half closed/opSen {(start, stop] or [start, stop)}
// -exclude range from all possible values ]start, stop[ =>
//    [-inf, start][stop, inf] or [-inf, start)(stop, inf]
//
// specialization requires comparison strategies (traits)
template<typename T, 
         typename LeftComparisionTrait, 
         typename RightComparisionTrait>
struct ValueRangeImpl
{
    using LTrait = LeftComparisionTrait;
    using RTrait = RightComparisionTrait;

    // to be like std::
    using value_type = T; // used type

    T start = {};
    T stop = {};

    // returns distance |start, stop|
    T length() const { return stop - start; }
    // returns whether u inside this
    bool doesInclude(T u) const;
    // returns corrected value (inserted into range, if it was outside)
    T included(T v) const;
};
Enter fullscreen mode Exit fullscreen mode

The doesInclude() and included() are:

template<typename T, 
         typename LeftComparisionTrait, 
         typename RightComparisionTrait>
bool ValueRangeImpl<T, LeftComparisionTrait, RightComparisionTrait>::doesInclude(T u) const
{
    return LTrait()(start, u) && RTrait()(u, stop);
}

template<typename T, 
         typename LeftComparisionTrait, 
         typename RightComparisionTrait>
T ValueRangeImpl<T, LeftComparisionTrait, RightComparisionTrait>::included(const T v) const
{
    return !LTrait()(start, v) 
                ? start : !RTrait()(v, stop) 
                ? stop : v;
}
Enter fullscreen mode Exit fullscreen mode

As the comparison strategy may be used std::less_equal:

// final value ranges implementation
// interval, with both boundary included: [a,b]
using ClosedValueRange = ValueRangeImpl<
    double,
    typename std::less_equal<double>,
    typename std::greater_equal<double>>;

// interval, with both boundary excluded: (a,b)
using OpenValueRange = ValueRangeImpl<
    double,
    typename std::less<double>,
    typename std::greater<double>>;

// interval, with excluded left boandary and included right one: (a,b]
using OpenClosedValueRange = ValueRangeImpl<
    double,
    typename std::less<double>,
    typename std::greater_equal<double>>;

// interval, with included left boandary and excluded right one: [a,b)
using ClosedOpenValueRange = ValueRangeImpl<
    double,
    typename std::less_equal<double>,
    typename std::greater<double>>;
Enter fullscreen mode Exit fullscreen mode

The simplest samples:

EXPECT_TRUE  (ClosedValueRange(1, 5).doesInclude(1)); 
EXPECT_TRUE  (ClosedValueRange(1, 5).doesInclude(5)); 
EXPECT_FALSE (OpenValueRange(1, 5).doesInclude(1));
EXPECT_FALSE (OpenValueRange(1, 5).doesInclude(5));
EXPECT_FALSE (OpenClosedValueRange(1, 5).doesInclude(1));
EXPECT_TRUE  (OpenClosedValueRange(1, 5).doesInclude(5));
EXPECT_TRUE  (ClosedOpenValueRange(1, 5).doesInclude(1));
EXPECT_FALSE (ClosedOpenValueRange(1, 5).doesInclude(5));
Enter fullscreen mode Exit fullscreen mode

Usage

SuitSellerSolution

Let's imagine that we are working under SuitSellerSolution. To put the suit on the market we have to describe its size.

// first approach to SuitSize
class SuitSize
{
public:
    std::string m_name;
    ClosedValueRange m_waist;
};

// three sizes for simplicity: 
SuitSize sizeS{"S", {78, 82}};
SuitSize sizeL{"M", {82, 86}};
SuitSize sizeM{"S", {86, 90}};

Enter fullscreen mode Exit fullscreen mode

But what about values 82 and 86? What size do they belong? Let' use partially closed intervals.

// SuitSize, which used apropriate interval
template<typename WaistWrap>
class SuitSizeImpl
{
public:
    std::string m_name;
    WaistWrap m_waist;
};

using SuitSizeS = SuitSizeImpl<ClosedOpenValueRange>;
using SuitSizeM = SuitSizeImpl<ClosedOpenValueRange>;
using SuitSizeL = SuitSizeImpl<ClosedValueRange>;

SuitSizeS sizeS{"S", {78, 82}};
SuitSizeS sizeM{"M", {82, 86}};
SuitSizeM sizeL{"L", {86, 90}};
Enter fullscreen mode Exit fullscreen mode

Greate! There are no conflicts in this SuitSize edition.

Vacation salary.

The next application calculates vacation salary. There is a requirement that vacation includes both boundaries, or somebody has a vacation from 11.07.2027 till 25.07.2027 exclusively, i.e. 26.07.2027 employee should be at work at 8:00 AM.
This implies the presence of class:

// this class describes vacation
class Vacation
{
    using Date = boost::gregorian::date; // may be your own class for Date representation
    // Date comparision functors:
    // start vacation comparision
    struct LeftDateCompare
    {
        bool operator()(const Date& d1, const Date& d2)
        {
            return d1 <= d2;
        }
    };
    // end vacation comparision
    struct RightDateCompare
    {
        bool operator()(const Date& d1, const Date& d2)
        {
            return d1 > d2; // open interval
        }
    };

    // Date interval [vacationStart, vacationStop)
    using DateValueRange = ValueRangeImpl<Date, LeftDateCompare, RightDateCompare>;

    DateValueRange m_duration;
};
Enter fullscreen mode Exit fullscreen mode

Then the system was demonstrated to users, they found, that usually vacation duration calculates inclusively. The only thing to fix is:

class Vacation
{
    ...
    // end vacation comparision
    struct RightDateCompare
    {
        bool operator()(const Date& d1, const Date& d2)
        {
            return d1 >= d2; // open interval
        }
    };
    ....
};
Enter fullscreen mode Exit fullscreen mode

I use ValueRange in the definition of the domain of a parametric function, and I will talk about it next time.

Top comments (0)