2017-02-28 103 views
4

我实际上想知道什么是在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?

+1

它是这个或查找表,只有6个记录,但这种方法似乎没问题。可能要重构幻数 –

+6

您不必与下一个条件与前一个的否定。还有'else',它不需要在那里,因为有'return'。 – LogicStuff

+2

@LogicStuff也许是为了清楚。 –

回答

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 
} 

编辑:我添加有趣的实施与structstd::find_if通过@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; 
} 
+0

很好的答案,谢谢,但看看我的问题最后增加了需求的底部,你如何改变这个代码来尊重自由空白? – BartekPL

+0

我编辑了我的答案。您可以为下限添加另一个数组。 –

+1

为了更容易阅读,你可以将你的数组聚合到一个'struct def {int lower; int upper;焦炭等级; } = {{0,40,'T'},...};'。 – YSC

3

对于这样小的数字,我会使用一个查找表:

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

return map[a]; 

一种替代对于较大或负数或sparsier范围是使用实际std::map

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

LogicStuff提出的更可读的解决方案是定义您的界限并通过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(
     std::begin(bounds), 
     std::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): 
+1

一组\ *节点\ *上的'std :: lower_bound'如何? – LogicStuff

+0

'std :: lower_bound'对我来说是新事物,不错 – BartekPL

+0

@LogicStuff我已将您的建议添加到我的答案中。这是你在想什么? – YSC

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

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

+0

但是如果范围之间有空闲空间,例如: 30-40 45-55 60-70,它回到我的代码的以前版本。我想知道是否可以用一些聪明的方式使用地图。 – BartekPL

+1

@BartekPL大概你需要返回_something_作为缺口(例如41..44),所以添加_something_作为地图的一个元素('std :: make_pair(_something_,41)')。 – TripeHound

+0

@TripeHound,是的,我知道这种方式,这个代码似乎是不错的选择 – BartekPL

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可以使用return
  2. 额外O101只是用于同时符合100自身。
+0

似乎罗伯特Eckhaus的更糟糕的版本回答 – BartekPL

+0

@BartekPL问题在哪里? –

+0

你只是不包括在你的答案免费空白的要求,但从我的问题表是完美的答案:) – BartekPL

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

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

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

+1

我最喜欢的矫枉过正的类型 –

+0

如果你需要超过一个小时构思查找表,你需要两个稍后再维护它,上周雇用的新人会在某个时候讨厌你。大多数时候,简单笨拙比聪明美丽更好。 – YSC