【问题标题】:Perfect hash function for strings known in advance预先知道的字符串的完美哈希函数
【发布时间】:2015-02-25 22:49:48
【问题描述】:

我有 4000 个字符串,我想用这些字符串创建一个完美的哈希表。字符串是预先知道的,所以我的第一个想法是使用一系列if 语句:

 if (name=="aaa")
      return 1;
 else if (name=="bbb")
      return 2;
        .
        .
        .
 // 4000th `if' statement

但是,这将是非常低效的。有没有更好的办法?

【问题讨论】:

  • 如果您先验地知道所有字符串,您确定需要对它们进行哈希处理吗?
  • 我需要将图形存储为邻接列表,所以我猜哈希表是必需的。@NPE
  • 散列是对这么多字符串执行此操作的最有效方法,如果您有关于字符串的先验信息,您可以使用 gperf 之类的东西创建一个好的散列函数。

标签: c++ string hash perfect-hash


【解决方案1】:

gperf 是一个可以做到这一点的工具:

GNU gperf 是一个完美的哈希函数生成器。对于给定的字符串列表,它以 C 或 C++ 代码的形式生成哈希函数和哈希表,用于根据输入字符串查找值。哈希函数是完美的,也就是说哈希表没有冲突,哈希表查找只需要单个字符串比较。

根据文档,gperf 用于为 GNU C、GNU C++、GNU Java、GNU Pascal、GNU Modula 3 和 GNU 缩进中的词法分析器生成保留关键字识别器。

Douglas C. Schmidt 在GPERF: A Perfect Hash Function Generator 中描述了它的工作方式。

【讨论】:

  • 我觉得这对我的应用来说太多了,我更喜欢更简单的东西
  • 我会将此线程添加为书签,以查看比 gperf 更简单的完美解决方案。
  • 运行时解决方案可能更容易构建。这个库显然在内存方面更有效率:cmph.sourceforge.net
【解决方案2】:

由于这个问题仍未得到解答,并且我即将向我的 HFT 平台添加相同的功能,因此我将分享我的 C++ 中的完美哈希算法清单。找到一个开放、灵活且无错误的实现比我想象的要难,所以我分享我还没有放弃的那些:

【讨论】:

    【解决方案3】:

    我相信@NPE 的回答是非常合理的,我怀疑它对你的应​​用来说太过分了,正如你所暗示的那样。

    考虑以下示例:假设您的“引擎”逻辑(即:应用程序的功能)包含在名为 engine.hpp 的文件中:

    // this is engine.hpp
    #pragma once
    #include <iostream>
    void standalone() {
      std::cout << "called standalone" << std::endl;
    }
    struct Foo {
      static void first() {
        std::cout << "called Foo::first()" << std::endl;
      }
      static void second() {
        std::cout << "called Foo::second()" << std::endl;
      }  
    };
    // other functions...
    

    假设您想根据地图调度不同的功能:

    "standalone" dispatches void standalone()
    "first" dispatches Foo::first()
    "second" dispatches Foo::second()
    # other dispatch rules...
    

    您可以使用以下 gperf 输入文件(我称之为“lookups.gperf”)来做到这一点:

    %{
    
    #include "engine.hpp"
    
    struct CommandMap {
        const char *name;
        void (*dispatch) (void);
    };
    
    %}
    
    %ignore-case
    %language=C++
    %define class-name Commands
    %define lookup-function-name Lookup
    struct CommandMap
    
    %%
    standalone, standalone
    first, Foo::first
    second, Foo::second
    

    然后你可以使用 gperf 使用一个简单的命令创建一个lookups.hpp 文件:

     gperf -tCG lookups.gperf > lookups.hpp
    

    一旦我设置好了,下面的main 子例程将根据我输入的内容调度命令:

    #include <iostream>
    #include "engine.hpp" // this is my application engine
    #include "lookups.hpp" // this is gperf's output
    
    int main() {
    
      std::string command;
    
      while(std::cin >> command) {
        auto match = Commands::Lookup(command.c_str(), command.size());
        if(match) {
          match->dispatch();
        } else {
          std::cerr << "invalid command" << std::endl;
        }
      }
    }
    

    编译:

     g++ main.cpp -std=c++11
    

    并运行它:

    $ ./a.out
    standalone
    called standalone
    first
    called Foo::first()
    Second
    called Foo::second()
    SECOND
    called Foo::second()
    first
    called Foo::first()
    frst
    invalid command
    

    请注意,一旦您生成了lookups.hpp,您的应用程序在 gperf 中就没有任何依赖关系了。

    免责声明:这个例子的灵感来自this site

    【讨论】:

      【解决方案4】:

      迟到总比没有好,我相信这现在终于回答了 OP 问题:

      只需使用 https://github.com/serge-sans-paille/frozen -- 一个用于 C++ 的不可变容器的编译时 (constexpr) 库(在后台使用“完美哈希”)。

      在我的测试中,它与著名的 GNU 的 gperf 完美的哈希 C 代码生成器配合使用。

      根据您的伪代码条款:

      #include <frozen/unordered_map.h>
      #include <frozen/string.h>
      
      constexpr frozen::unordered_map<frozen::string, int, 2> olaf = {
          {"aaa", 1},
          {"bbb", 2},
          .
          .
          .
          // 4000th element
      };
      
      return olaf.at(name);
      

      将在 O(1) 时间内做出响应,而不是 OP 的 O(n) -- O(n) 假设编译器不会优化你的 if 链,它可能会这样做)

      【讨论】:

        猜你喜欢
        • 2014-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-09
        相关资源
        最近更新 更多