如何将范围映射到值?

时间:2017-02-28 13:15:07

标签: c++ c++11

我实际上想知道什么是在C ++中正确地将字母分配给范围的最佳方法。

例如,我们有这样的规模:

enter image description here

我们可以用最简单的方式完成任务:

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';

但是,你有更好的想法吗?

当我们有更大的数字和更多的字母时(我认为如果陈述仍然令人讨厌......)?或者如果范围之间存在空闲空间,例如30-40 45-55 60-70?

5 个答案:

答案 0 :(得分:4)

您可以使用简单数组来排序范围和输出

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
}

编辑:我正在使用@YSC建议的structstd::find_if添加有趣的实现

#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;
}

答案 1 :(得分:3)

几乎在所有情况下都适用的一般解决方案是以自描述的方式定义每个符号的边界,然后使用std::lower_bound找到适当的值:

#include <iostream>
#include <algorithm>

char grade(int a)
{
    constexpr std::pair<int, char> bounds[] = {
        { 39,  'T' },
        { 54,  'D' },
        { 69,  'P' },
        { 79,  'A' },
        { 89,  'E' },
        { 99,  'O' },
        { 100, '\0' },
    };

    return std::lower_bound(begin(bounds),end(bounds),std::make_pair(a, '\0'))->second;
}

Test (demo)

grade(39): T
grade(40): D
grade(54): D
grade(55): P
grade(69): P
grade(70): A
grade(79): A
grade(80): E
grade(89): E
grade(90): O
grade(99): O
grade(100): 

针对此特定问题的另一个解决方案可能是,因为此处涉及的数字很小,所以使用简单的查找表:

static const char map[] =
    "............" // make 40 of them
    "TTTTTTTTTTTTTTT" // 40..54
    "DDDDDDDDDDDDDDD" // 55..69
    // etc
    ;

return map[a];

最后,对于较大或负数或较稀疏范围的替代方法是使用实​​际的std::map

#include <map>
static const std::map<int, char> map = {
    { 40, 'T' },
    { 41, 'T' },
    // ...
    { 100, 'O' }
};
return map.at(a);

答案 2 :(得分:3)

这是C ++ 14的答案。一切都可以翻译成C ++ 11,不那么漂亮。

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接受一个投影和一个比较函数对象,并返回一个比较函数对象,该对象将投影应用于std::less<>或比较函数对象。

这在排序或搜索时很有用,因为C ++算法需要比较函数对象,而投影很容易编写。

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允许您重载函数对象。

template<class T>
struct valid_range_t {
  T start, finish;
};

这表示有效范围。我可以使用std::pair,但我更喜欢有意义的类型。

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 );
}

现在find_value将一对迭代器带到排列有非重叠范围的ranged_value_t类型结构。

然后返回一个指向第一个(也就是唯一的)值的条目的指针,该值的(半开)范围包含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

这个答案相对于替代方案的优势:

  • 我们不会创建不必要的值对象。如果价值对象是昂贵的,大的或非平凡的可构造的,这很重要。
  • 创建表格的语法很简单
  • 表格几乎可以是任何格式。您可以从文件中解析它们; find_value函数只接受迭代器(并且更喜欢它们是随机访问)。
  • 我们可以通过允许省略下限来扩充。我们必须向valid_range_t添加一个标记(或使用可选项),并在我们检查find_value时在s中使用该标记,并将构造函数添加到valid_range_t以使其成为易于使用。

增加它以支持半开和间隔时间需要一些工作。作为第二次检查,我很想把它变成find_value

重叠间隔也需要一些工作。我会在开始时(s)执行lower_bound,在结束时执行upper_bound(f)。

我发现这种东西最适合数据驱动设计;在C ++代码中对此进行硬编码是一个糟糕的计划。相反,您使用配置,并编写代码以进行验证并由该配置驱动。

答案 3 :(得分:2)

如果你想要想要一个易于扩展的列表,那么你可以使用查找表并循环遍历它:

#include <iostream>
#include <utility>
#include <vector>

char returnGrade(int a)
{
    std::vector<std::pair<char, int>> chars = {
        std::make_pair('T', 40),
        std::make_pair('D', 55),
        std::make_pair('P', 70),
        std::make_pair('A', 80),
        std::make_pair('E', 90),
        std::make_pair('O', 100)
    };
    for(auto itr = chars.begin(); itr != chars.end(); ++itr)
    {
        if(a < itr->second)
          return itr->first;
    }
    //return the last one if we passed the loop.
    return chars.back().first;
}

int main() {
    //test it..
    std::cout << returnGrade(20) << " " << returnGrade(45) << " " << returnGrade(90)  << " " << returnGrade(100);
    return 0;
}

当然,你不应该在函数的本地查找表,但你明白了。

答案 4 :(得分:0)

不,您可以使用简单array和简单for

int array[ 8 ]  = { 0,  40,  55,   70,  80,  90, 100,  101 };
char score[ 7 ] = {  'T', 'D',  'P', 'A', 'E', 'O', 'O' };

int input = 50;
for( int index = 0; index < 8; ++index ){
    if( input < array[ index ] ){
        std::cout << score[ index - 1 ];  // D
        break;
    }
}

注意

  1. 您可以使用std:cout
  2. 代替return
  3. 刚刚用于的额外O101也匹配100本身。