KeyEqual在std :: unordered_set / std :: unordered_map中的用途

时间:2019-07-06 11:29:21

标签: c++ stl

我知道这可能是一个模糊的问题,但是我想知道当自定义比较器对std中的哈希容器有用时在现实情况下是什么情况。 我知道它在有序容器中很有用,但是对于哈希容器,这似乎有点不可思议。

这样做的原因是,根据比较器相等的元素的哈希值必须相同,并且我相信,在大多数情况下,这实际上意味着将lookup / insert元素转换为某种通用表示形式(更快,更容易实行)。

例如:

  • 不区分大小写的字符串集:如果要正确地散列,则无论如何都需要对整个字符串进行大写/小写。
  • 一组分数(其中2/3 == 42/63):您需要将42/63转换为2/3,然后对其进行哈希处理...

因此,我想知道是否有人可以提供一些自定义std::unordered_模板参数有用性的示例,以便在以后编写的代码中识别出这些模式。

注1:“对称参数”(std::map允许自定义比较器,因此std::unordred_也应自定义)是我考虑的,但我认为这没有说服力。

注2:为了简洁起见,我在帖子中混合了两种比较器(<==),我知道std::map使用<和{{1} }使用std::unordered_map

3 个答案:

答案 0 :(得分:10)

按照https://en.cppreference.com/w/cpp/container/unordered_set

  

在内部,元素不是按任何特定顺序排序的,而是   整理成水桶。元素放入哪个存储桶取决于   完全取决于其价值。这样可以快速访问   各个元素,因为一旦计算出哈希值,它就是指   元素所在的确切存储桶。

因此,哈希函数定义了元素将最终存储在的存储桶中,但是一旦确定存储桶,为了找到元素,将使用operator ==

基本上,operator ==用于解决哈希冲突,因此,您需要哈希函数和operator ==保持一致。此外,如果运算符operator ==说两个元素相等,则该集合将不允许重复。

关于自定义方面,我认为set中不区分大小写的string的想法是一个好主意:给定两个字符串,您将需要提供不区分大小写的哈希函数来允许set确定存储字符串的存储桶。然后,您将需要提供一个自定义KeyEqual,以允许集合实际检索元素。

过去,我不得不处理的一种情况是允许用户插入字符串,跟踪其插入顺序但避免重复的一种方式。因此,给定这样的结构:

struct keyword{
  std::string value;
  int sequenceCounter;
};

您只想根据value检测重复项。我想到的解决方案之一是带有自定义比较器/哈希函数的unordered_set,该函数仅使用value。这使我可以在允许插入之前检查密钥是否存在。

答案 1 :(得分:2)

一种有趣的用法是为一组给定的对象定义内存有效索引(术语的数据库意义)。

示例

假设我们有一个程序,其中包含此类的N个对象的集合:

struct Person {
    // each object has a unique firstName/lastName pair
    std::string firstName;
    std::string lastName;

    // each object has a unique ssn value
    std::string socialSecurityNumber;

    // each object has a unique email value
    std::string email;
}

我们需要通过任何唯一属性的值来有效地检索对象。

实施比较

假设字符串比较是恒定时间(字符串长度有限),则给出时间复杂度。

1)单unordered_map

使用由单个键索引的单个map(例如:email):

std::unordered_map<std::string,Person> indexedByEmail;
  • 时间复杂度:使用email以外的任何唯一属性进行查询都需要遍历地图:平均O(N)。
  • 内存使用情况:email值重复。可以通过使用带有自定义哈希和比较的单个set来避免这种情况(请参见3)。

2)多个unordered_map,没有自定义哈希和比较

具有每个唯一属性的映射,以及默认的哈希和比较:

std::unordered_map<std::pair<std::string,std::string>, Person> byName;
std::unordered_map<std::string, const Person*> byEmail;
std::unordered_map<std::string, const Person*> bySSN;
  • 时间复杂度:通过使用适当的地图,可以按average O(1)进行任何唯一属性的查找。
  • 由于所有string重复,内存使用效率低下。

3)多个unordered_set,自定义哈希和比较:

使用自定义哈希和比较,我们定义了不同的unordered_set,它将仅哈希和比较对象的特定字段。这些集合可用于执行查找,就像将项目存储在map中一样(如2)一样,但是无需重复任何字段。

using StrHash = std::hash<std::string>;
// --------------------
struct PersonNameHash {
    std::size_t operator()(const Person& p) const {
        // not the best hashing function in the world, but good enough for demo purposes.
        return StrHash()(p.firstName) + StrHash()(p.lastName);
    }
};
struct PersonNameEqual {
    bool operator()(const Person& p1, const Person& p2) const {
        return (p1.firstName == p2.firstName) && (p1.lastName == p2.lastName);
    }
};
std::unordered_set<Person, PersonNameHash, PersonNameEqual> byName;

// --------------------
struct PersonSsnHash {
    std::size_t operator()(const Person* p) const {
        return StrHash()(p->socialSecurityNumber);
    }
};
struct PersonSsnEqual {
    bool operator()(const Person* p1, const Person* p2) const {
        return p1->socialSecurityNumber == p2->socialSecurityNumber;
    }
};
std::unordered_set<const Person*, PersonSsnHash, PersonSsnEqual> bySSN;

// --------------------
struct PersonEmailHash {
    std::size_t operator()(const Person* p) const {
        return StrHash()(p->email);
    }
};
struct PersonEmailEqual {
    bool operator()(const Person* p1, const Person* p2) const {
        return p1->email == p2->email;
    }
};
std::unordered_set<const Person*,PersonEmailHash,PersonEmailEqual> byEmail;
  • 时间复杂度:通过任何唯一属性进行的查找仍然是O(1)平均值。
  • 内存使用率:优于2):没有string复制。

Live demo

答案 2 :(得分:-1)

哈希函数本身以某种方式提取特征,比较器的工作是区分特征是否相同
使用“外壳”数据,您可能不需要修改比较器
简要地说:在数据上放一个功能外壳。功能负责比较

事实上,我不太了解您对问题的描述。我的演讲不可避免地在逻辑上感到困惑。敬请谅解。 :)