I'm actually wondering what is a best way to assign letter to a range properly in C++.
For example, we have that scale:
We can do assignment in simplest way:
if(a < 40)
return 'T';
else if(a < 55)
return 'D';
else if(a < 70)
return 'P';
else if(a < 80)
return 'A';
else if(a < 90)
return 'E';
else if(a <= 100)
return 'O';
However, do you have some better ideas to make this?
And what when we have bigger numbers and more letters (I think that if statements can be still annoying...)? Or what if there are free spaces between ranges, e.g. 30-40 45-55 60-70?
You can use simple arrays for sorted ranges and outputs
char getGrade (int grade) {
int upper[] = { 40, 55, 70, 80, 90, 100 };
int lower[] = { 0, 40, 55, 70, 80, 90 };
char grades[] = { 'T', 'D', 'P', 'A', 'E', 'O' };
for (int i = 0; i< 6; i++)
if ((grade< upper[i]) && (grade >= lower[i]))
return grades[i];
return 'X'; // no grade assigned
}
Edit: I'm adding interesting implementation with struct
and std::find_if
suggested by @YSC
#include <iostream>
#include <algorithm>
#include <vector>
struct Data{ int lower; int upper; char grade; };
char getGrade (int grade, std::vector<Data> data) {
auto it = std::find_if(
data.begin(),
data.end(),
[&grade](Data d) { return (d.lower<= grade) && (d.upper > grade); }
);
if (it == data.end())
return 'X'; // not found
else
return it->grade;
}
int main () {
const std::vector<Data> myData = { { 0, 40, 'T'} , { 40, 55, 'D'}, {55, 70, 'P'}, {70, 80, 'A'}, {80, 90, 'E'}, {90, 101, 'O'} };
std::cout << getGrade(20, myData) << std::endl;
return 0;
}
This is a C++14 answer. Everything can be translated to C++11, just less pretty.
template<class F, class Base=std::less<>>
auto order_by( F&& f, Base&& b={} ) {
return
[f=std::forward<F>(f), b = std::forward<Base>(b)]
(auto const& lhs, auto const& rhs)
->bool
{
return b( f(lhs), f(rhs) );
};
}
order_by
takes a projection and optionally a comparison function object, and returns a comparison function object that applies the projection then either std::less<>
or the comparison function object.
This is useful when sorting or searching, as C++ algorithms require comparison function objects, while projections are easy to write.
template<class A, class B>
struct binary_overload_t:A,B{
using A::operator();
using B::operator();
binary_overload_t( A a, B b ):A(std::move(a)), B(std::move(b)) {}
};
template<class A, class B>
binary_overload_t< A, B >
binary_overload( A a, B b ) {
return { std::move(a), std::move(b) };
}
binary_overload
lets you overload function objects.
template<class T>
struct valid_range_t {
T start, finish;
};
This represents a valid range. I could just use std::pair
, but I prefer types with meaning.
template<class T, class V>
struct ranged_value_t {
valid_range_t<T> range;
V value;
};
template<class T, class It>
auto find_value( It begin, It end, T const& target )
-> decltype(std::addressof( begin->value ))
{
// project target into target
// and a ranged value onto the lower end of the range
auto projection = binary_overload(
[]( auto const& ranged )->T const& {
return ranged.range.finish;
},
[]( T const& t )->T const& {
return t;
}
);
//
auto it = std::upper_bound( begin, end,
target,
order_by( projection )
);
if (it == end) return nullptr;
if (target < it->range.start) return nullptr;
return std::addressof( it->value );
}
Now find_value
takes a pair of iterators to ranged_value_t
type structures arranged with non-overlapping ranges.
It then return a pointer to the entry of the first (and hence only) value whose (half open) range contains target
.
ranged_value_t<int, char> table[]={
{{0,40}, 'T'},
{{41,55}, 'D'},
{{56,70}, 'P'},
{{71,80}, 'A'},
{{81,90}, 'E'},
{{91,101}, 'O'}
};
auto* ptr = find_value( std::begin(table), std::end(table), 83 );
if (ptr) std::cout << *ptr << "\n"; else std::cout << "nullptr\n";
Live example.
The advantages of this answer over alternatives:
find_value
function just takes iterators (and prefers them to be random access).valid_range_t
(or use an optional) and consume it in find_value
when we check s
, and add constructors to valid_range_t
to make it easy to use.Augmenting it to support both half-open and closed intervals would take a bit of work. I'd be tempted to hack it into find_value
as a second check.
Overlapping intervals also takes a bit of work. I'd do a lower_bound on start (s
) and an upper_bound on finish (f
).
I find this kind of stuff is best suited by data-driven design; hardcoding this in C++ code is a bad plan. Instead, you consume configuration, and you write your code to validate and be driven by that configuration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With