我编写了一个应该将字符串转换为数字的函数。我看到两种可能的变体来写它:
int convert(const std::string input) {
if (input == "one") {
return 1;
} else if (input == "two") {
return 2;
}
// etc.
return 0;
}
或者
int convert(const std::string input) {
static const map<string, int> table = {
{"one", 1},
{"two", 2}
// etc.
}
const auto result = table.find(input);
if (result == table.end())
{
return 0;
}
return result->second;
}
哪种方式更有效/可接受/可读?
答案 0 :(得分:21)
答案在很大程度上取决于你将支持多少不同的字符串。
一些字符串:使用if-else。稍后理解代码所需的努力很少。
很多字符串:创建地图。与阅读巨大的if-else结构的努力相比,理解代码的努力很小。可能,您必须经常扩展此列表。添加数据需要更少的输入。
我不确定C ++的地图是如何使用字符串作为键的。在最坏的情况下,两者都具有相同的性能。如果列表变得非常庞大,您可能会想到创建字符串的哈希值并将其用作键。这可能会大大提高性能。你必须确保不会发生碰撞。 (良好的散列算法和64位散列大小应该足够了。)现代地图实现可能已经这样做了。
答案 1 :(得分:8)
对于一小组文本,我会使用一个简单的查找表:
struct LookupTable {
const char* text;
int value;
};
const LookupTable table[] = {
{ "one", 1 },
{ "two", 2 }
};
int convert(const char* text) {
if (!text) return 0;
for (int i=0; i<sizeof(table)/sizeof(LookupTable); i++) {
if (strcasecmp(text, table[i].text) == 0 ) {
return table[i].value;
}
}
return 0;
}
对于大量文本,我会考虑使用std::unordered_map<std::string,int>
,也许自定义哈希函数(bkdr哈希或elf哈希对单词很好)。
sizeof
,请使用现代的for-loop:
int convert(const char* text) {
if (!text) return 0;
for (auto& entry: table) {
if (strcasecmp(text, entry.text) == 0 ) {
return entry.value;
}
}
return 0;
}
答案 2 :(得分:6)
if-else
(或switch
(如果可用)适用于小案例,您还可以控制测试的顺序,以防最常见的测试可以减少搜索很快,你可以先测试它们。
在许多情况下,switch
远远优于if-else
列表。两者都更容易阅读,也可能更快。虽然switch
不是string
的最佳选择。
然而,您可以打开enum
而不是使用字符串;除了map
之外,这肯定是更好的方法。
map
或std::unordered_map
对于大量可能性或在运行时需要更新这些可能性时要好得多。
答案 3 :(得分:3)
对于少量可能的输入值,我更倾向于解决方案1,它很简单并且可能具有最佳性能。
如果值列表变得太大,那么您真正需要的是整数和书写数字之间的转换器,这实际上是一个不同的故事(请参阅NathanOliver评论中引用的“Humanizer”库
答案 4 :(得分:3)
哪种方式更有效/可接受/可读?
if
/ else
解决方案是最有效的,如果你只有几个值,并且当然非常简单,特别是对于那些不习惯标准库的人来说,但它很快就会陷入混乱
因此,只要您达到5个或更多项目,就切换到使用容器。
警告:不幸的是,std::string_view
,这将避免内存分配,仍然不是标准的;为简单起见,我将使用std::string
,但如果内存分配存在问题,std::string_view
或自定义CStr
类会更好。
有3种有效选择:
std::map<std::string, int>
和std::unordered_map<std::string, int>
是最直观的选择,不清楚哪个会更快std::vector<std::pair<std::string, int>>
(已排序)将始终比std::map<std::string, int>
因此,如果效率是一个问题:
int convert(std::string const& name) {
static std::vector<std::pair<std::string, int>> const Table = []() {
std::vector<std::pair<std::string, int>> result = {
{ "one", 1 },
{ "two", 2 },
{ "three", 3 },
{ "four", 4 }
};
std::sort(result.begin(), result.end());
return result;
}();
auto const it =
std::lower_bound(Table.begin(), Table.end(), std::make_pair(name, 0));
if (it != Table.end() and it->first == name) {
return it->second;
}
return 0;
}
毕竟,排序数组是执行二进制搜索的最有效方法,因为它具有更好的缓存行为。出于同样的原因,它也应该在小输入上优于std::unordered_map
。
当然,它的可读性稍差。
答案 5 :(得分:2)
我建议map
。主要原因是它在这个词的两种可能含义中都能更好地扩展。
如果您将来需要添加更多条件(可能是这样),使用地图会更易于维护和管理。此外,它允许运行时修改查找表,这在某些情况下非常有用。
我不得不在我正在开发的东西中处理类似的问题,其中像这样的查找必须可以通过子类来修改。我认为地图提供了更大的灵活性。 Maps允许我定义一个虚函数,如getLookup()
,它返回一个查找表。在该函数中,我可以保留一个特定于该类类型的静态映射(我在第一次调用时需要的方式设置)。如果您正在考虑这种应用程序,那么我强烈建议使用链接映射。如果链在继承中完全无法管理。您将开始询问&#34;如何更改X解决的内容?&#34;迟早,除了意大利面之外,几乎没有什么实际的答案。
另一条评论:考虑unordered_map
。对于这个用例,范围迭代似乎不太可能。
答案 6 :(得分:2)
if-else搜索的复杂度为O(n),而地图搜索O(log n)。 此外,当列表变长时,if-else语句将变得不可读。因此,地图更好。
另一方面,关于函数声明中的参数:
int convert(const std::string input)
我会将其更改为pass-by-constant-reference而不是pass-by-constant-copy以提高效率:
int convert(const std::string& input)
答案 7 :(得分:2)
我在这里提出了许多不同答案的crude measurements以及我自己的一些想法,对于GCC上数字“一”到“九”的情况发现这是最快的:
int convert(const std::string& input) {
static const std::array<std::string, 9> numbers
= {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
auto find_result = std::find(numbers.begin(), numbers.end(), input);
if (find_result == numbers.end())
return 0;
return std::distance(numbers.begin(), find_result) + 1;
}
我碰巧认为它也是合理的“可接受”和“可读”。
任何建议之间的表现都没有太大差异。
结果与Clang相似。有趣的是,Visual Studio 2015完全不同。
答案 8 :(得分:1)
这是X macros非常适合的事情之一:
这与@Calvin的查找表方法类似,无需在多个位置跟踪多组数据。
//alphabetically sorted by string X macro
#define MAP_AS_ENUM(e,v,s) MYENUM_##e,
#define MAP_AS_STRING(e,v,s) s,
#define MAP_AS_VALUE(e,v,s) v,
#define MYMAP(OP) \
OP(NONE, -1,"") \
OP(FIVE, 5, "five") \
OP(FOUR, 4, "four") \
OP(ONE, 1, "one") \
OP(THREE, 3, "three") \
OP(TWO, 2, "two") \
OP(ZERO, 0, "zero")
enum myenums{ MYMAP(MAP_AS_ENUM) };
char *mystrings[] = { MYMAP(MAP_AS_STRING) };
char myvalues[]={ MYMAP(MAP_AS_VALUE) };
//now you can use a binary search on mystrings to get the index
//which will correspond to the associated enum