static const std :: map <string,int =“”> vs if-elseif

时间:2016-05-03 15:09:41

标签: c++ c++11 if-statement dictionary

我编写了一个应该将字符串转换为数字的函数。我看到两种可能的变体来写它:

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

哪种方式更有效/可接受/可读?

9 个答案:

答案 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之外,这肯定是更好的方法。

mapstd::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