是否可以比使用hashmap更快地将字符串映射到int?

时间:2013-04-22 06:59:39

标签: c++ performance algorithm hashmap

我知道我不应该优化我的计划中的每一个位置,所以请将此问题视为“学术”

我每个都有最多100个字符串和整数,类似:

MSFT 1
DELL 2
HP   4
....
ABC  58

这个集合是预先初始化的,这意味着一旦创建它就永远不会改变。初始化set后我使用它非常密集,所以很快就能快速查找。字符串很短,最多30个字符。映射的int也是有限的,介于1到100之间。

至少知道字符串是预先初始化的并且永远不会改变它应该可以“找到”导致“一篮子一项”映射的哈希函数,但可能还有其他黑客。

我能想象的一个优化 - 我只能读取第一个符号。例如,如果“DELL”是唯一以“D”开头的字符串,并且我收到了类似“D ***”的内容,而不是我甚至不需要阅读字符串!它显而易见地“戴尔”。这种查找必须比“hashmap lookup”快得多。 (在这里,我假设我们只接收哈希中的符号,但情况并非总是如此)

我的问题是否已准备就绪或易于实施?我正在使用c ++和boost。

更新我已经检查并发现,对于我的交易限制,股票代码是12个符号,而不是如上所述的30个符号。然而,其他交换可能允许稍微长一些的符号,所以有一个算法可以继续处理多达20个字符长的代码。

7 个答案:

答案 0 :(得分:35)

哈希表 [1] 原则上是最快的方法。

可以然后编译Perfect Hash Function,因为你事先知道完整的域名。

使用完美哈希,不需要发生冲突,因此您可以将哈希表存储在线性数组中!

通过适当的调整,你可以

  • 将所有哈希元素放在有限的空间中,直接寻址潜在的选项
  • 在O(1)
  • 中进行反向查找

用于生成Perfect Hash函数的'old-school'工具将是gperf(1)。维基百科列出了有关该主题的更多资源。

  

由于所有辩论我进行了演示

     

下载NASDAQ ticker symbols并从该集合中获取100个随机样本,按如下方式应用gperf:

gperf -e ' \015' -L C++ -7 -C -E -k '*,1,$' -m 100 selection > perfhash.cpp
     

得到157的哈希值MAX_HASH_VALUE和一个包含多个项目的直接字符串查找表。这里的只是哈希函数用于演示目的:

inline unsigned int Perfect_Hash::hash (register const char *str, register unsigned int len) {
  static const unsigned char asso_values[] = {
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156,  64,  40,   1,  62,   1,
       41,  18,  47,   0,   1,  11,  10,  57,  21,   7,
       14,  13,  24,   3,  33,  89,  11,   0,  19,   5,
       12,   0, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156, 156,
      156, 156, 156, 156, 156, 156, 156, 156, 156
    };
  register int hval = len;

  switch (hval) {
      default: hval += asso_values[(unsigned char)str[4]];   /*FALLTHROUGH*/
      case 4:  hval += asso_values[(unsigned char)str[3]];   /*FALLTHROUGH*/
      case 3:  hval += asso_values[(unsigned char)str[2]+1]; /*FALLTHROUGH*/
      case 2:  hval += asso_values[(unsigned char)str[1]];   /*FALLTHROUGH*/
      case 1:  hval += asso_values[(unsigned char)str[0]];   break;
  }
  return hval;
}
     

它的效率确实不高。请查看 full source at github: https://gist.github.com/sehe/5433535

     

请注意,这也是一个完美的哈希,所以会有没有碰撞


  

问。 [...] 它显然是“戴尔”。这样的查找必须比“hashmap lookup”快得多。

答:如果使用简单的std::map,则净效果为前缀搜索(因为第一个字符不匹配时的字典字符串比较快捷方式)。在分类容器中进行二进制搜索也是如此。


[1] PS 。对于100个字符串,由于改进的 Locality of Reference ,带有std::searchstd::lower_bound的字符串排序数组可能会更快/更快。请查阅您的个人资料结果,看看是否适用。

答案 1 :(得分:18)

对sehe的帖子的小补充:

  

如果使用简单的std::map,则净效果是前缀搜索(因为第一个字符不匹配时的词典字符串比较快捷方式)。在分类容器中进行二进制搜索也是如此。

您可以利用前缀搜索更高效。 std::map和天真二进制搜索的问题在于,它们将为每个单独的比较冗余地读取相同的前缀,从而使整体搜索O( m log n m 是搜索字符串的长度。

这就是为什么hashmap比较大型集合的这两种方法的原因。但是,有一个数据结构执行冗余前缀比较,实际上需要将每个前缀恰好比较一次:前缀(搜索)树,通常称为trie,在O( m )中查找长度 m 的单个字符串是可行的,与完美散列的哈希表相同的渐近运行时。

具有完美哈希的trie或(直接查找)哈希表是否更适合您的目的是一个分析问题。

答案 2 :(得分:0)

好吧,您可以将字符串存储在二叉树中并在那里搜索。 虽然这具有O(log n)理论性能,但如果你只有几个密钥,那么可能在实践中要快得多,这些密钥真的很长,而且前几个字符已经不同了。 / p>

即。比较密钥比计算哈希函数便宜。

此外,还有CPU缓存效果,这可能(或可能不)有益。

但是,使用相当便宜的哈希函数,哈希表将很难被击败。

答案 3 :(得分:0)

是!

哈希必须遍历你的字符串并构建一个哈希值。使用链接 [Wiki:Trie] 中所解释的trie时,只需要在链接结构上遵循路径而不进行任何过度计算。如果它是压缩的trie,如页面末尾所解释的那样,当一个单词的首字母是一个单词(你谈到的DELL案例)时,它会计入一个计数。预处理稍高,但在运行时间内表现最佳。

更多优点:
1.如果您要查找的字符串不存在,您知道第一个字符与现有字符串不同(不需要继续计算)
2.实施后,向t​​rie添加更多字符串是直截了当的。

答案 4 :(得分:0)

如上所述的标准散列映射以及完美散列函数受到散列函数本身的相对较慢执行的影响。草绘的完美散列函数,例如最多有5个随机访问数组。

测量或计算散列函数和字符串比较的速度是有意义的,假设功能是通过一个散列函数评估,一个表中的查找和线性搜索通过包含字符串的(链接)列表完成的和他们的索引来解决哈希冲突。 在许多情况下,最好使用更简单但更快的散列函数并接受更多的字符串比较,而不是使用更好但更慢的散列函数,并且使用更少(标准散列映射)或甚至只有一个(完美散列)比较。

您将在my site上找到相关主题“switch on string”的讨论,以及使用通用测试平台的一系列解决方案,使用宏作为免费的C / C ++源,可在运行时解决问题。我也在考虑预编译器。

答案 5 :(得分:0)

(然而)sehe's回答的另一个小补充:

除了Perfect Hash函数之外,还有这个Minimal Perfect Hash函数,分别是C Minimal Perfect Hash Function。它与gperf几乎相同,只是:

  

gperf有点不同,因为它被设想为小组密钥创建非常快速的完美哈希函数,并且CMPH库被设想为非常大的密钥集创建最小的完美哈希函数

     

CMPH库将最新,更高效的算法封装在易于使用,生产质量快的API中。该库旨在处理无法放入主内存的大型条目。它已成功用于构建具有超过1亿个密钥的集合的最小完美散列函数,并且我们打算将此数字扩展到数十亿的密钥

来源:http://cmph.sourceforge.net/

答案 6 :(得分:-1)

如果在编译时知道字符串,则可以使用枚举:

enum
{
  Str1,
  Str2
};

const char *Strings = {
  "Str1",
  "Str2"
};

使用一些宏技巧,您可以消除在两个位置重新创建表格的冗余(使用文件包含和#undef)。

然后查找可以像索引数组一样快地实现:

const char *string = Strings[Str1]; // set to "Str1"

这将具有最佳的查找时间和参考位置。