In everyday life, we encounter various ranges or diapasons or ranges - time ranges, money ranges, etc. I propose to write an implementation for them.
Introduction
Every time in 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:
struct ValueRange
{
double start = 0.0;
double stop = 0.0;
double length() const { return stop - start; }
bool includes(double u) const { return start <= u && u <= stop; }
double included(double v) const;
};
But somebody may want works with float
s rather than double
s. So the code will change to:
template<typename T>
struct ValueRangeT
{
using Type = T;
using value_type = T;
T start{};
T stop{};
T length() const { return stop - start; }
bool includes(T u) const { return start <= u && u <= stop; }
T included(T v) const;
};
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:
template<typename T, typename LeftComparisionTrait, typename RightComparisionTrait>
struct ValueRangeImpl
{
using LTrait = LeftComparisionTrait;
using RTrait = RightComparisionTrait;
using value_type = T;
T start = {};
T stop = {};
T length() const { return stop - start; }
bool includes(T u) const;
T included(T v) const;
};
The includes()
and included()
are:
template<typename T, typename LeftComparisionTrait, typename RightComparisionTrait>
bool ValueRangeImpl<T, LeftComparisionTrait, RightComparisionTrait>::includes(T u) const
{
return LTrait()(start, u) && RTrait()(stop, u);
}
template<typename T, typename LeftComparisionTrait, typename RightComparisionTrait>
T ValueRangeImpl<T, LeftComparisionTrait, RightComparisionTrait>::included(const T v) const
{
return !LTrait()(start, v) ? start : !RTrait()(stop, v) ? stop : v;
}
As the comparison strategy may be used std::less_equal
:
using ClosedValueRange = ValueRangeImpl<
double,
typename std::less_equal<double>,
typename std::greater_equal<double>>;
using OpenValueRange = ValueRangeImpl<
double,
typename std::less<double>,
typename std::greater<double>>;
using OpenClosedValueRange = ValueRangeImpl<
double,
typename std::less<double>,
typename std::greater_equal<double>>;
using ClosedOpenValueRange = ValueRangeImpl<
double,
typename std::less_equal<double>,
typename std::greater<double>>;
The simplest samples:
EXPECT_TRUE (ClosedValueRange(1, 5).includes(1));
EXPECT_TRUE (ClosedValueRange(1, 5).includes(5));
EXPECT_FALSE (OpenValueRange(1, 5).includes(1));
EXPECT_FALSE (OpenValueRange(1, 5).includes(5));
EXPECT_FALSE (OpenClosedValueRange(1, 5).includes(1));
EXPECT_TRUE (OpenClosedValueRange(1, 5).includes(5));
EXPECT_TRUE (ClosedOpenValueRange(1, 5).includes(1));
EXPECT_FALSE (ClosedOpenValueRange(1, 5).includes(5));
Usage
Let's imagine that we are working under SuitSellerSolution
. To put the suit on the market, we have to describe its size.
class SuitSize
{
public:
std::string m_name;
ClosedValueRange m_waist;
};
SuitSize sizeS{"S", {78, 82}};
SuitSize sizeL{"M", {82, 86}};
SuitSize sizeM{"S", {86, 90}};
But what about values 82
and 86
? What size do they belong? Let's use partially closed intervals.
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}};
Great! There are no conflicts in this SuitSize
edition.
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:
class Vacation
{
using Date = boost::gregorian::date; struct LeftDateCompare
{
bool operator()(const Date& d1, const Date& d2)
{
return d1 <= d2;
}
};
struct RightDateCompare
{
bool operator()(const Date& d1, const Date& d2)
{
return d1 > d2; }
};
using DateValueRange = ValueRangeImpl<Date, LeftDateCompare, RightDateCompare>;
DateValueRange m_duration;
};
While then the system was demonstrated to users, they found, that usually vacation duration calculates inclusively. The only thing to fix is:
class Vacation
{
...
struct RightDateCompare
{
bool operator()(const Date& d1, const Date& d2)
{
return d1 >= d2; }
};
....
};
I use ValueRange
in the definition of the domain of a parametric function, and I will talk about it next time.
History
- 6th December, 2020: Initial version